diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 42176a760e1b..ffe557d66f3b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -371,49 +371,6 @@ jobs: if: ${{ always() }} run: cat community-build/dotty-community-build-deps || true - community_build_forward_compat: - runs-on: [self-hosted, Linux] - container: - image: lampepfl/dotty:2021-03-22 - options: --cpu-shares 4096 - volumes: - - ${{ github.workspace }}/../../cache/sbt:/root/.sbt - - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - - ${{ github.workspace }}/../../cache/general:/root/.cache - if: "github.event_name == 'schedule' && github.repository == 'lampepfl/dotty' - || ( - github.event_name == 'pull_request' - && !contains(github.event.pull_request.body, '[skip ci]') - && !contains(github.event.pull_request.body, '[skip community_build]') - && contains(github.event.pull_request.body, '[test_forward_compat]') - ) - || ( - github.event_name == 'workflow_dispatch' - && github.repository == 'lampepfl/dotty' - )" - - steps: - - name: Reset existing repo - run: git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/lampepfl/dotty" && git reset --hard FETCH_HEAD || true - - - name: Checkout cleanup script - uses: actions/checkout@v2 - - - name: Cleanup - run: .github/workflows/cleanup.sh - - - name: Git Checkout - uses: actions/checkout@v2 - - - name: Add SBT proxy repositories - run: cp -vf .github/workflows/repositories /root/.sbt/ ; true - - - name: Test - run: | - git submodule sync - git submodule update --init --recursive --jobs 7 - ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestForwardCompat" - test_sbt: runs-on: [self-hosted, Linux] container: @@ -513,7 +470,7 @@ jobs: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - ${{ github.workspace }}/../../cache/general:/root/.cache - needs: [test_non_bootstrapped, test, mima, community_build_a, community_build_b, community_build_c, community_build_forward_compat, test_sbt, test_java8] + needs: [test_non_bootstrapped, test, mima, community_build_a, community_build_b, community_build_c, test_sbt, test_java8] if: "(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'lampepfl/dotty'" env: NIGHTLYBUILD: yes diff --git a/.gitmodules b/.gitmodules index 971eb25a9466..aadf222714ad 100644 --- a/.gitmodules +++ b/.gitmodules @@ -216,37 +216,6 @@ [submodule "community-build/community-projects/spire"] path = community-build/community-projects/spire url = https://github.com/dotty-staging/spire.git -[submodule "community-build/community-projects/munit-forward-compat"] - path = community-build/community-projects/munit-forward-compat - url = https://github.com/dotty-staging/munit.git -[submodule "community-build/community-projects/discipline-forward-compat"] - path = community-build/community-projects/discipline-forward-compat - url = https://github.com/dotty-staging/discipline.git -[submodule "community-build/community-projects/discipline-munit-forward-compat"] - path = community-build/community-projects/discipline-munit-forward-compat - url = https://github.com/dotty-staging/discipline-munit.git -[submodule "community-build/community-projects/discipline-specs2-forward-compat"] - path = community-build/community-projects/discipline-specs2-forward-compat - url = https://github.com/dotty-staging/discipline-specs2.git -[submodule "community-build/community-projects/simulacrum-scalafix-forward-compat"] - path = community-build/community-projects/simulacrum-scalafix-forward-compat - url = https://github.com/dotty-staging/simulacrum-scalafix.git -[submodule "community-build/community-projects/cats-forward-compat"] - path = community-build/community-projects/cats-forward-compat - url = https://github.com/dotty-staging/cats.git -[submodule "community-build/community-projects/cats-mtl-forward-compat"] - path = community-build/community-projects/cats-mtl-forward-compat - url = https://github.com/dotty-staging/cats-mtl.git -[submodule "community-build/community-projects/coop-forward-compat"] - path = community-build/community-projects/coop-forward-compat - url = https://github.com/dotty-staging/coop.git -[submodule "community-build/community-projects/cats-effect-3-forward-compat"] - path = community-build/community-projects/cats-effect-3-forward-compat - url = https://github.com/dotty-staging/cats-effect.git - branch = series/3.x -[submodule "community-build/community-projects/scalacheck-forward-compat"] - path = community-build/community-projects/scalacheck-forward-compat - url = https://github.com/dotty-staging/scalacheck [submodule "community-build/community-projects/http4s"] path = community-build/community-projects/http4s url = https://github.com/dotty-staging/http4s.git diff --git a/NOTICE.md b/NOTICE.md index 64b5f9122db7..64ebae49efe5 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -88,6 +88,10 @@ major authors were omitted by oversight. docs/js/. Please refer to the license header of the concerned files for details. + * dotty.tools.dotc.coverage: Coverage instrumentation utilities have been + adapted from the scoverage plugin for scala 2 [5], which is under the + Apache 2.0 license. + * The Dotty codebase contains parts which are derived from the ScalaPB protobuf library [4], which is under the Apache 2.0 license. @@ -96,3 +100,4 @@ major authors were omitted by oversight. [2] https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt [3] https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt [4] https://github.com/lampepfl/dotty/pull/5783/files +[5] https://github.com/scoverage/scalac-scoverage-plugin diff --git a/community-build/community-projects/cats-effect-3-forward-compat b/community-build/community-projects/cats-effect-3-forward-compat deleted file mode 160000 index 2510979ee03d..000000000000 --- a/community-build/community-projects/cats-effect-3-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2510979ee03d10e60011812698567885547d85d7 diff --git a/community-build/community-projects/cats-forward-compat b/community-build/community-projects/cats-forward-compat deleted file mode 160000 index 6bbbc1e3477b..000000000000 --- a/community-build/community-projects/cats-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6bbbc1e3477b7da6b58a97844f773c3c445b4e5e diff --git a/community-build/community-projects/cats-mtl-forward-compat b/community-build/community-projects/cats-mtl-forward-compat deleted file mode 160000 index 149f002c8774..000000000000 --- a/community-build/community-projects/cats-mtl-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 149f002c8774b61df87cb846455d94ae858b3b54 diff --git a/community-build/community-projects/coop-forward-compat b/community-build/community-projects/coop-forward-compat deleted file mode 160000 index 8700dde73ef1..000000000000 --- a/community-build/community-projects/coop-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8700dde73ef16a6135014840c4e33361dbd015d7 diff --git a/community-build/community-projects/discipline-forward-compat b/community-build/community-projects/discipline-forward-compat deleted file mode 160000 index afd001326789..000000000000 --- a/community-build/community-projects/discipline-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit afd00132678985341db210b56f3b2ead1a8405c2 diff --git a/community-build/community-projects/discipline-munit-forward-compat b/community-build/community-projects/discipline-munit-forward-compat deleted file mode 160000 index 38ea89226b8d..000000000000 --- a/community-build/community-projects/discipline-munit-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 38ea89226b8ddedc891b160f75d57ae5177f19a1 diff --git a/community-build/community-projects/discipline-specs2-forward-compat b/community-build/community-projects/discipline-specs2-forward-compat deleted file mode 160000 index e689c3e809a8..000000000000 --- a/community-build/community-projects/discipline-specs2-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e689c3e809a89a03cdbbb3a1771e33148715f6c7 diff --git a/community-build/community-projects/munit-forward-compat b/community-build/community-projects/munit-forward-compat deleted file mode 160000 index 92f3ad9e8261..000000000000 --- a/community-build/community-projects/munit-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 92f3ad9e8261b4c142a551baaf61ef5fed84d36a diff --git a/community-build/community-projects/scala-parallel-collections b/community-build/community-projects/scala-parallel-collections index b26e74437f0c..a6bd648bb188 160000 --- a/community-build/community-projects/scala-parallel-collections +++ b/community-build/community-projects/scala-parallel-collections @@ -1 +1 @@ -Subproject commit b26e74437f0cadc3c92e9378e69197f3494b1811 +Subproject commit a6bd648bb188a65ab36be07e956e52fe25f64d67 diff --git a/community-build/community-projects/scalacheck-forward-compat b/community-build/community-projects/scalacheck-forward-compat deleted file mode 160000 index 976db31cd549..000000000000 --- a/community-build/community-projects/scalacheck-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 976db31cd549328167a90ecc6f5f31efa83cd845 diff --git a/community-build/community-projects/simulacrum-scalafix-forward-compat b/community-build/community-projects/simulacrum-scalafix-forward-compat deleted file mode 160000 index 2515271c46ad..000000000000 --- a/community-build/community-projects/simulacrum-scalafix-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2515271c46ad46512a43d20e1e8ae0793433cf0b diff --git a/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala index d60def1a28f1..6a0c54c4b00b 100644 --- a/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala +++ b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala @@ -17,10 +17,10 @@ object CommunityBuildRunner: * for more infrastructural details. */ extension (self: CommunityProject) def run()(using suite: CommunityBuildRunner): Unit = - if self.requiresExperimental && !self.compilerSupportExperimental then + if self.requiresExperimental && !compilerSupportExperimental then log(s"Skipping ${self.project} - it needs experimental features unsupported in this build.") return - self.dependencies().foreach(_.publish()) + self.dependencies.foreach(_.publish()) self.testOnlyDependencies().foreach(_.publish()) suite.runProject(self) @@ -45,7 +45,6 @@ trait CommunityBuildRunner: val project = projectDef.project val command = projectDef.binaryName val arguments = projectDef.buildCommands - val compilerVersion = projectDef.compilerVersion @annotation.tailrec def execTimes(task: () => Int, timesToRerun: Int): Boolean = diff --git a/community-build/src/scala/dotty/communitybuild/Main.scala b/community-build/src/scala/dotty/communitybuild/Main.scala index 7c3a39261eb0..852cee46af22 100644 --- a/community-build/src/scala/dotty/communitybuild/Main.scala +++ b/community-build/src/scala/dotty/communitybuild/Main.scala @@ -57,7 +57,7 @@ object Main: val (toRun, ignored) = allProjects.partition( p => p.docCommand != null - && (!p.requiresExperimental || p.compilerSupportExperimental) + && (!p.requiresExperimental || compilerSupportExperimental) ) val paths = toRun.map { project => diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index c5e7997d0b21..3a0ec6c280b7 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -6,10 +6,13 @@ import java.nio.charset.StandardCharsets.UTF_8 lazy val communitybuildDir: Path = Paths.get(sys.props("user.dir")) -lazy val testedCompilerVersion: String = +lazy val compilerVersion: String = val file = communitybuildDir.resolve("scala3-bootstrapped.version") new String(Files.readAllBytes(file), UTF_8) +lazy val compilerSupportExperimental: Boolean = + compilerVersion.contains("SNAPSHOT") || compilerVersion.contains("NIGHTLY") + lazy val sbtPluginFilePath: String = // Workaround for https://github.com/sbt/sbt/issues/4395 new File(sys.props("user.home") + "/.sbt/1.0/plugins").mkdirs() @@ -36,21 +39,17 @@ sealed trait CommunityProject: val testCommand: String val publishCommand: String val docCommand: String - val dependencies: () => List[CommunityProject] + val dependencies: List[CommunityProject] val testOnlyDependencies: () => List[CommunityProject] val binaryName: String val runCommandsArgs: List[String] = Nil val requiresExperimental: Boolean val environment: Map[String, String] = Map.empty - val compilerVersion: String final val projectDir = communitybuildDir.resolve("community-projects").resolve(project) - final val compilerSupportExperimental: Boolean = - compilerVersion.contains("SNAPSHOT") || compilerVersion.contains("NIGHTLY") - final def publishDependencies(): Unit = - dependencies().foreach(_.publish()) + dependencies.foreach(_.publish()) /** Publish this project to the local Maven repository */ final def publish(): Unit = @@ -88,11 +87,10 @@ end CommunityProject final case class MillCommunityProject( project: String, baseCommand: String, - dependencies: () => List[CommunityProject] = () => Nil, + dependencies: List[CommunityProject] = Nil, testOnlyDependencies: () => List[CommunityProject] = () => Nil, ignoreDocs: Boolean = false, requiresExperimental: Boolean = false, - compilerVersion: String = testedCompilerVersion ) extends CommunityProject: override val binaryName: String = "./mill" override val testCommand = s"$baseCommand.test" @@ -107,14 +105,12 @@ final case class SbtCommunityProject( project: String, sbtTestCommand: String, extraSbtArgs: List[String] = Nil, - dependencies: () => List[CommunityProject] = () => Nil, + dependencies: List[CommunityProject] = Nil, testOnlyDependencies: () => List[CommunityProject] = () => Nil, sbtPublishCommand: String = null, sbtDocCommand: String = null, scalacOptions: List[String] = SbtCommunityProject.scalacOptions, requiresExperimental: Boolean = false, - compilerVersion: String = testedCompilerVersion, - isForwardCompatProject: Boolean = false ) extends CommunityProject: override val binaryName: String = "sbt" @@ -123,7 +119,6 @@ final case class SbtCommunityProject( private val baseCommand = "clean; set Global/logLevel := Level.Error; set Global/updateOptions ~= (_.withLatestSnapshots(false)); " - ++ (if isForwardCompatProject then "set Global / isForwardCompatProject := true; " else "") ++ (if scalacOptions.isEmpty then "" else s"""set Global/scalacOptions ++= $scalacOptionsString;""") ++ s"++$compilerVersion!; " @@ -151,19 +146,6 @@ final case class SbtCommunityProject( s"--addPluginSbtFile=$sbtPluginFilePath" ) - def forwardCompat: SbtCommunityProject = - this.copy( - project = project + "-forward-compat", - dependencies = () => dependencies().map(forwardCompatMapping), - testOnlyDependencies = () => testOnlyDependencies().map(forwardCompatMapping), - isForwardCompatProject = true - ) - - def withScalaRelease(release: String): SbtCommunityProject = - this.copy( - scalacOptions = scalacOptions ++ Seq("-scala-output-version", release) - ) - object SbtCommunityProject: def scalacOptions = List( "-Xcheck-macros", @@ -184,89 +166,89 @@ object projects: lazy val utest = MillCommunityProject( project = "utest", - baseCommand = s"utest.jvm[$testedCompilerVersion]", + baseCommand = s"utest.jvm[$compilerVersion]", ignoreDocs = true ) lazy val sourcecode = MillCommunityProject( project = "sourcecode", - baseCommand = s"sourcecode.jvm[$testedCompilerVersion]", + baseCommand = s"sourcecode.jvm[$compilerVersion]", ignoreDocs = true ) lazy val oslib = MillCommunityProject( project = "os-lib", - baseCommand = s"os.jvm[$testedCompilerVersion]", - dependencies = () => List(utest, sourcecode) + baseCommand = s"os.jvm[$compilerVersion]", + dependencies = List(utest, sourcecode) ) lazy val oslibWatch = MillCommunityProject( project = "os-lib", - baseCommand = s"os.watch[$testedCompilerVersion]", - dependencies = () => List(utest, sourcecode), + baseCommand = s"os.watch[$compilerVersion]", + dependencies = List(utest, sourcecode), ignoreDocs = true ) lazy val ujson = MillCommunityProject( project = "upickle", - baseCommand = s"ujson.jvm[$testedCompilerVersion]", - dependencies = () => List(geny) + baseCommand = s"ujson.jvm[$compilerVersion]", + dependencies = List(geny) ) lazy val upickle = MillCommunityProject( project = "upickle", - baseCommand = s"upickle.jvm[$testedCompilerVersion]", - dependencies = () => List(geny, utest) + baseCommand = s"upickle.jvm[$compilerVersion]", + dependencies = List(geny, utest) ) lazy val upickleCore = MillCommunityProject( project = "upickle", - baseCommand = s"core.jvm[$testedCompilerVersion]", - dependencies = () => List(geny, utest) + baseCommand = s"core.jvm[$compilerVersion]", + dependencies = List(geny, utest) ) lazy val upickleImplicits = MillCommunityProject( project = "upickle", - baseCommand = s"implicits.jvm[$testedCompilerVersion]", - dependencies = () => List(upickleCore, ujson) + baseCommand = s"implicits.jvm[$compilerVersion]", + dependencies = List(upickleCore, ujson) ) lazy val upack = MillCommunityProject( project = "upickle", - baseCommand = s"upack.jvm[$testedCompilerVersion]", - dependencies = () => List(ujson, upickleCore) + baseCommand = s"upack.jvm[$compilerVersion]", + dependencies = List(ujson, upickleCore) ) lazy val geny = MillCommunityProject( project = "geny", - baseCommand = s"geny.jvm[$testedCompilerVersion]", - dependencies = () => List(utest) + baseCommand = s"geny.jvm[$compilerVersion]", + dependencies = List(utest) ) lazy val fansi = MillCommunityProject( project = "fansi", - baseCommand = s"fansi.jvm[$testedCompilerVersion]", - dependencies = () => List(utest, sourcecode), + baseCommand = s"fansi.jvm[$compilerVersion]", + dependencies = List(utest, sourcecode), ignoreDocs = true ) lazy val pprint = MillCommunityProject( project = "PPrint", - baseCommand = s"pprint.jvm[$testedCompilerVersion]", - dependencies = () => List(fansi), + baseCommand = s"pprint.jvm[$compilerVersion]", + dependencies = List(fansi), ignoreDocs = true ) lazy val requests = MillCommunityProject( project = "requests-scala", - baseCommand = s"requests[$testedCompilerVersion]", - dependencies = () => List(geny, utest, ujson, upickleCore) + baseCommand = s"requests[$compilerVersion]", + dependencies = List(geny, utest, ujson, upickleCore) ) lazy val cask = MillCommunityProject( project = "cask", - baseCommand = s"cask[$testedCompilerVersion]", - dependencies = () => List(utest, geny, sourcecode, pprint, upickle, upickleImplicits, upack, requests) + baseCommand = s"cask[$compilerVersion]", + dependencies = List(utest, geny, sourcecode, pprint, upickle, upickleImplicits, upack, requests) ) lazy val scas = MillCommunityProject( @@ -288,8 +270,6 @@ object projects: sbtDocCommand = forceDoc("jvm") ) - lazy val scalacheckForwardCompat = scalacheck.forwardCompat.withScalaRelease("3.0") - lazy val scalatest: SbtCommunityProject = SbtCommunityProject( project = "scalatest", sbtTestCommand = @@ -310,7 +290,7 @@ object projects: // org.scalatest.Outcome // Problem parsing scalatest.dotty/target/scala-3.0.0-M2/src_managed/main/org/scalatest/concurrent/ConductorFixture.scala:[602..624..3843], documentation may not be generated. // dotty.tools.dotc.core.MissingType: - dependencies = () => List(scalaXml), + dependencies = List(scalaXml), testOnlyDependencies = () => List(scalatestplusJunit, scalatestplusTestNG) ) @@ -319,21 +299,21 @@ object projects: sbtTestCommand = "scalatestPlusScalaCheckJVM/test", sbtPublishCommand = "scalatestPlusScalaCheckJVM/publishLocal", sbtDocCommand = "scalatestPlusScalaCheckJVM/doc", - dependencies = () => List(scalatest, scalacheck) + dependencies = List(scalatest, scalacheck) ) lazy val scalatestplusJunit = SbtCommunityProject( project = "scalatestplus-junit", sbtTestCommand = "scalatestplus-junit/test", sbtPublishCommand = "scalatestplus-junit/publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val scalatestplusTestNG = SbtCommunityProject( project = "scalatestplus-testng", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val scalaXml = SbtCommunityProject( @@ -369,7 +349,7 @@ object projects: project = "minitest", sbtTestCommand = "test", sbtDocCommand = aggregateDoc("lawsJVM")("minitestJVM"), - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) lazy val fastparse = SbtCommunityProject( @@ -428,7 +408,7 @@ object projects: project = "sconfig", sbtTestCommand = "sconfigJVM/test", sbtDocCommand = "sconfigJVM/doc", - dependencies = () => List(scalaCollectionCompat) + dependencies = List(scalaCollectionCompat) ) lazy val zio = SbtCommunityProject( @@ -436,7 +416,7 @@ object projects: sbtTestCommand = "testJVMDotty", sbtDocCommand = forceDoc("coreJVM"), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Xcheck-macros"), - dependencies = () => List(izumiReflect) + dependencies =List(izumiReflect) ) lazy val munit = SbtCommunityProject( @@ -444,17 +424,15 @@ object projects: sbtTestCommand = "testsJVM/test;testsJS/test;", sbtPublishCommand = "munitJVM/publishLocal; munitJS/publishLocal; munitScalacheckJVM/publishLocal; munitScalacheckJS/publishLocal; junit/publishLocal", sbtDocCommand = "junit/doc; munitJVM/doc", - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) - lazy val munitForwardCompat = munit.forwardCompat.withScalaRelease("3.0") - lazy val scodecBits = SbtCommunityProject( project = "scodec-bits", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", sbtDocCommand = "coreJVM/doc", - dependencies = () => List(munit), + dependencies = List(munit), ) lazy val scodec = SbtCommunityProject( @@ -463,7 +441,7 @@ object projects: // Adds package sbtDocCommand = "coreJVM/doc", scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(munit, scodecBits), + dependencies = List(munit, scodecBits), ) lazy val scalaParserCombinators = SbtCommunityProject( @@ -496,7 +474,7 @@ object projects: // [error] class scalaz.iteratee.Iteratee cannot be unpickled because no class file was found sbtDocCommand = forceDoc("effectJVM"), - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) lazy val endpoints4s = SbtCommunityProject( @@ -510,16 +488,14 @@ object projects: sbtTestCommand = "ciJVM", sbtPublishCommand = "publishLocal", sbtDocCommand = ";coreJVM/doc ;lawsJVM/doc ;kernelJVM/doc", - dependencies = () => List(cats, coop, disciplineSpecs2, scalacheck) + dependencies = List(cats, coop, disciplineSpecs2, scalacheck) ) - lazy val catsEffect3ForwardCompat = catsEffect3.forwardCompat.copy(compilerVersion = "3.0.2") - lazy val scalaParallelCollections = SbtCommunityProject( project = "scala-parallel-collections", sbtTestCommand = "test", sbtDocCommand = forceDoc("core"), - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) lazy val scalaCollectionCompat = SbtCommunityProject( @@ -531,8 +507,8 @@ object projects: lazy val scalaJava8Compat = SbtCommunityProject( project = "scala-java8-compat", // the fnGen subproject must be built with 2.12.x - sbtTestCommand = s"++2.12.14; ++$testedCompilerVersion; set fnGen/dependencyOverrides := Nil; test", - sbtPublishCommand = s"++2.12.14; ++$testedCompilerVersion; set fnGen/dependencyOverrides := Nil; publishLocal", + sbtTestCommand = s"++2.12.14; ++$compilerVersion; set fnGen/dependencyOverrides := Nil; test", + sbtPublishCommand = s"++2.12.14; ++$compilerVersion; set fnGen/dependencyOverrides := Nil; publishLocal", scalacOptions = Nil // avoid passing Scala 3 options to Scala 2.12 in fnGen subproject ) @@ -548,81 +524,67 @@ object projects: sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "set every credentials := Nil;coreJVM/publishLocal;coreJS/publishLocal", scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) - lazy val disciplineForwardCompat = discipline.forwardCompat.withScalaRelease("3.0") - lazy val disciplineMunit = SbtCommunityProject( project = "discipline-munit", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = () => List(discipline, munit) + dependencies = List(discipline, munit) ) - lazy val disciplineMunitForwardCompat = disciplineMunit.forwardCompat.withScalaRelease("3.0") - lazy val disciplineSpecs2 = SbtCommunityProject( project = "discipline-specs2", sbtTestCommand = "test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = () => List(discipline), + dependencies = List(discipline), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init") ) - lazy val disciplineSpecs2ForwardCompat = disciplineSpecs2.forwardCompat.withScalaRelease("3.0") - lazy val simulacrumScalafixAnnotations = SbtCommunityProject( project = "simulacrum-scalafix", sbtTestCommand = "annotation/test:compile;annotationJS/test:compile", sbtPublishCommand = "annotation/publishLocal;annotationJS/publishLocal", ) - lazy val simulacrumScalafixAnnotationsForwardCompat = simulacrumScalafixAnnotations.forwardCompat.withScalaRelease("3.0") - lazy val cats = SbtCommunityProject( project = "cats", sbtTestCommand = "set Global/scalaJSStage := FastOptStage;buildJVM;validateAllJS", sbtPublishCommand = "catsJVM/publishLocal;catsJS/publishLocal", - dependencies = () => List(discipline, disciplineMunit, scalacheck, simulacrumScalafixAnnotations), + dependencies = List(discipline, disciplineMunit, scalacheck, simulacrumScalafixAnnotations), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init") // disable -Ysafe-init, due to -Xfatal-warning ) - lazy val catsForwardCompat = cats.forwardCompat.withScalaRelease("3.0") - lazy val catsMtl = SbtCommunityProject( project = "cats-mtl", sbtTestCommand = "testsJVM/test;testsJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal;lawsJVM/publishLocal;lawsJS/publishLocal", - dependencies = () => List(cats, disciplineMunit) + dependencies = List(cats, disciplineMunit) ) - lazy val catsMtlForwardCompat = catsMtl.forwardCompat.copy(compilerVersion = "3.0.2") - lazy val coop = SbtCommunityProject( project = "coop", sbtTestCommand = "test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = () => List(cats, catsMtl) + dependencies = List(cats, catsMtl) ) - lazy val coopForwardCompat = coop.forwardCompat.withScalaRelease("3.0") - // 'Sciss/Lucre' with its dependencies: lazy val scissEqual = SbtCommunityProject( project = "Equal", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissFingerTree = SbtCommunityProject( project = "FingerTree", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissLog = SbtCommunityProject( @@ -635,42 +597,42 @@ object projects: project = "Model", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissNumbers = SbtCommunityProject( project = "Numbers", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissSerial = SbtCommunityProject( project = "Serial", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissAsyncFile = SbtCommunityProject( project = "AsyncFile", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scissLog, scalatest), + dependencies = List(scissLog, scalatest), ) lazy val scissSpan = SbtCommunityProject( project = "Span", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scissSerial, scalatest), + dependencies = List(scissSerial, scalatest), ) lazy val scalaSTM = SbtCommunityProject( project = "scala-stm", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatestplusJunit), + dependencies = List(scalatestplusJunit), ) lazy val scissLucre = SbtCommunityProject( @@ -678,14 +640,14 @@ object projects: sbtTestCommand = "adjunctJVM/test;baseJVM/test;confluentJVM/test;coreJVM/test;dataJVM/test;exprJVM/test;geomJVM/test;lucre-bdb/test;testsJVM/test", extraSbtArgs = List("-Dde.sciss.lucre.ShortTests=true"), sbtPublishCommand = "adjunctJVM/publishLocal;baseJVM/publishLocal;confluentJVM/publishLocal;coreJVM/publishLocal;dataJVM/publishLocal;exprJVM/publishLocal;geomJVM/publishLocal;lucre-bdb/publishLocal", - dependencies = () => List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), + dependencies = List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), ) lazy val izumiReflect = SbtCommunityProject( project = "izumi-reflect", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val perspective = SbtCommunityProject( @@ -693,12 +655,12 @@ object projects: // No library with easy typeclasses to verify data against exist for Dotty, so no tests yet // Until then I guess this mainly serves to check that it still compiles at all sbtTestCommand = "dottyPerspectiveExamples/compile", - dependencies = () => List(cats) + dependencies = List(cats) ) lazy val akka = SbtCommunityProject( project = "akka", - extraSbtArgs = List(s"-Dakka.build.scalaVersion=$testedCompilerVersion"), + extraSbtArgs = List(s"-Dakka.build.scalaVersion=$compilerVersion"), sbtTestCommand = List( "set every targetSystemJdk := true", // selectively disable -Xfatal-warnings due to deprecations @@ -708,13 +670,13 @@ object projects: "akka-actor-tests/Test/compile", ).mkString("; "), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(scalatest, scalatestplusJunit, scalatestplusScalacheck) + dependencies = List(scalatest, scalatestplusJunit, scalatestplusScalacheck) ) lazy val monocle = SbtCommunityProject( project = "Monocle", sbtTestCommand = "coreJVM/test; macrosJVM/test; testJVM/test", - dependencies = () => List(cats, munit, discipline, disciplineMunit) + dependencies = List(cats, munit, discipline, disciplineMunit) ) lazy val protoquill = SbtCommunityProject( @@ -722,7 +684,7 @@ object projects: extraSbtArgs = List("-Dcommunity=true", "-DcommunityRemote=true", "-Dquill.macro.stdout=true"), sbtTestCommand = "runCommunityBuild", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), scalacOptions = List("-language:implicitConversions"), // disabled -Ysafe-init, due to bug in macro ) @@ -730,28 +692,28 @@ object projects: project = "onnx-scala", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val playJson = SbtCommunityProject( project = "play-json", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest, scalatestplusScalacheck), + dependencies = List(scalatest, scalatestplusScalacheck), ) lazy val munitCatsEffect = SbtCommunityProject( project = "munit-cats-effect", sbtTestCommand = "ce3JVM/test; ce3JS/test", sbtPublishCommand = "ce3JVM/publishLocal; ce3JS/publishLocal", - dependencies = () => List(munit, catsEffect3) + dependencies = List(munit, catsEffect3) ) lazy val scalacheckEffect = SbtCommunityProject( project = "scalacheck-effect", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(cats, catsEffect3, munit, scalacheck) + dependencies = List(cats, catsEffect3, munit, scalacheck) ) lazy val fs2 = SbtCommunityProject( @@ -759,28 +721,28 @@ object projects: sbtTestCommand = "coreJVM/test; coreJS/test", // io/test requires JDK9+ sbtPublishCommand = "coreJVM/publishLocal; coreJS/publishLocal", scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) + dependencies = List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) ) lazy val libretto = SbtCommunityProject( project = "libretto", sbtTestCommand = "core/test; examples/compile", sbtPublishCommand = "core/publishLocal; examples/publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val jacksonModuleScala = SbtCommunityProject( project = "jackson-module-scala", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalaJava8Compat, scalatest) + dependencies = List(scalaJava8Compat, scalatest) ) lazy val specs2 = SbtCommunityProject( project = "specs2", sbtTestCommand = "core/testOnly -- exclude ci", sbtPublishCommand = "core/publishLocal", - dependencies = () => List() + dependencies = List() ) lazy val spire = SbtCommunityProject( @@ -788,7 +750,7 @@ object projects: sbtTestCommand = "test", sbtPublishCommand = "publishLocal", scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Xcheck-macros"), - dependencies = () => List(cats, disciplineMunit) + dependencies = List(cats, disciplineMunit) ) lazy val http4s = SbtCommunityProject( @@ -796,24 +758,11 @@ object projects: sbtTestCommand = "tests/test; server/test; client/test; ember-core/test; ember-server/test; ember-client/test; circe/test", sbtPublishCommand = "publishLocal", scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(cats, catsEffect3, fs2, disciplineMunit, scalacheckEffect) + dependencies = List(cats, catsEffect3, fs2, disciplineMunit, scalacheckEffect) ) end projects -lazy val forwardCompatMapping = Map[CommunityProject, CommunityProject]( - projects.scalacheck -> projects.scalacheckForwardCompat, - projects.munit -> projects.munitForwardCompat, - projects.discipline -> projects.disciplineForwardCompat, - projects.disciplineMunit -> projects.disciplineMunitForwardCompat, - projects.disciplineSpecs2 -> projects.disciplineSpecs2ForwardCompat, - projects.simulacrumScalafixAnnotations -> projects.simulacrumScalafixAnnotationsForwardCompat, - projects.cats -> projects.catsForwardCompat, - projects.catsMtl -> projects.catsMtlForwardCompat, - projects.coop -> projects.coopForwardCompat, - projects.catsEffect3 -> projects.catsEffect3ForwardCompat, -) - def allProjects = List( projects.utest, projects.sourcecode, @@ -832,7 +781,6 @@ def allProjects = List( projects.scas, projects.intent, projects.scalacheck, - projects.scalacheckForwardCompat, projects.scalatest, projects.scalatestplusScalacheck, projects.scalatestplusJunit, @@ -849,7 +797,6 @@ def allProjects = List( projects.sconfig, projects.zio, projects.munit, - projects.munitForwardCompat, projects.scodecBits, projects.scodec, projects.scalaParserCombinators, @@ -857,23 +804,16 @@ def allProjects = List( projects.scalaz, projects.endpoints4s, projects.catsEffect3, - projects.catsEffect3ForwardCompat, projects.scalaParallelCollections, projects.scalaCollectionCompat, projects.scalaJava8Compat, projects.verify, projects.discipline, - projects.disciplineForwardCompat, projects.disciplineMunit, - projects.disciplineMunitForwardCompat, projects.disciplineSpecs2, - projects.disciplineSpecs2ForwardCompat, projects.simulacrumScalafixAnnotations, - projects.simulacrumScalafixAnnotationsForwardCompat, projects.cats, - projects.catsForwardCompat, projects.catsMtl, - projects.catsMtlForwardCompat, projects.coop, projects.scissEqual, projects.scissFingerTree, @@ -900,7 +840,6 @@ def allProjects = List( projects.jacksonModuleScala, projects.specs2, projects.coop, - projects.coopForwardCompat, projects.spire, projects.http4s ) diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 5be7f936ab66..657d49db3172 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -100,17 +100,3 @@ class CommunityBuildTestC: @Test def verify = projects.verify.run() @Test def xmlInterpolator = projects.xmlInterpolator.run() end CommunityBuildTestC - -@Category(Array(classOf[TestCategory])) -class CommunityBuildTestForwardCompat: - @Test def catsEffect3ForwardCompat = projects.catsEffect3ForwardCompat.run() - @Test def catsForwardCompat = projects.catsForwardCompat.run() - @Test def catsMtlForwardCompat = projects.catsMtlForwardCompat.run() - @Test def coopForwardCompat = projects.coopForwardCompat.run() - @Test def disciplineForwardCompat = projects.disciplineForwardCompat.run() - @Test def disciplineMunitForwardCompat = projects.disciplineMunitForwardCompat.run() - @Test def disciplineSpecs2ForwardCompat = projects.disciplineSpecs2ForwardCompat.run() - @Test def munitForwardCompat = projects.munitForwardCompat.run() - @Test def scalacheckForwardCompat = projects.scalacheckForwardCompat.run() - @Test def simulacrumScalafixAnnotationsForwardCompat = projects.simulacrumScalafixAnnotationsForwardCompat.run() -end CommunityBuildTestForwardCompat diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index 77a0bf36397e..b63a080dcead 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -53,6 +53,7 @@ trait BCodeIdiomatic { case "16" => asm.Opcodes.V16 case "17" => asm.Opcodes.V17 case "18" => asm.Opcodes.V18 + case "19" => asm.Opcodes.V19 } lazy val majorVersion: Int = (classfileVersion & 0xFF) diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index f00c54e584f9..8a525b2f8091 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -33,6 +33,8 @@ import scala.tools.asm.tree._ import tpd._ import StdNames._ import dotty.tools.io._ +import scala.tools.asm.MethodTooLargeException +import scala.tools.asm.ClassTooLargeException class GenBCode extends Phase { @@ -512,7 +514,7 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim * (c) tear down (closing the classfile-writer and clearing maps) * */ - def run(t: Tree): Unit = { + def run(t: Tree)(using Context): Unit = { this.tree = t // val bcodeStart = Statistics.startTimer(BackendStats.bcodeTimer) @@ -558,18 +560,29 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim * (c) dequeue one at a time from queue-2, convert it to byte-array, place in queue-3 * (d) serialize to disk by draining queue-3. */ - private def buildAndSendToDisk(needsOutFolder: Boolean) = { - - feedPipeline1() - // val genStart = Statistics.startTimer(BackendStats.bcodeGenStat) - (new Worker1(needsOutFolder)).run() - // Statistics.stopTimer(BackendStats.bcodeGenStat, genStart) - - (new Worker2).run() - - // val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer) - drainQ3() - // Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart) + private def buildAndSendToDisk(needsOutFolder: Boolean)(using Context) = { + try + feedPipeline1() + // val genStart = Statistics.startTimer(BackendStats.bcodeGenStat) + (new Worker1(needsOutFolder)).run() + // Statistics.stopTimer(BackendStats.bcodeGenStat, genStart) + + (new Worker2).run() + + // val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer) + drainQ3() + // Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart) + catch + case e: MethodTooLargeException => + val method = + s"${e.getClassName.replaceAll("/", ".")}.${e.getMethodName}" + val msg = + s"Generated bytecode for method '$method' is too large. Size: ${e.getCodeSize} bytes. Limit is 64KB" + report.error(msg) + case e: ClassTooLargeException => + val msg = + s"Class '${e.getClassName.replaceAll("/", ".")}' is too large. Constant pool size: ${e.getConstantPoolCount}. Limit is 64K entries" + report.error(msg) } diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 0462ed9d08d5..45254b1e2325 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -4558,7 +4558,14 @@ class JSCodeGen()(using genCtx: Context) { val module = annot.argumentConstantString(0).getOrElse { unexpected("could not read the module argument as a string literal") } - val path = annot.argumentConstantString(1).fold[List[String]](Nil)(parsePath) + val path = annot.argumentConstantString(1).fold { + if (annot.arguments.sizeIs < 2) + parsePath(sym.defaultJSName) + else + Nil + } { pathName => + parsePath(pathName) + } val importSpec = Import(module, path) annot.argumentConstantString(2).fold[js.JSNativeLoadSpec] { importSpec diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index bee783af258f..c9dbdaf68812 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala @@ -119,7 +119,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { if (kind != overallKind) { bad = true report.error( - em"export overload conflicts with export of $firstSym: they are of different types ($kind / $overallKind)", + em"export overload conflicts with export of $firstSym: they are of different types (${kind.tryToShow} / ${overallKind.tryToShow})", info.pos) } } diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index df2b586ac6ae..c25d271f39a9 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -47,15 +47,10 @@ class CompilationUnit protected (val source: SourceFile) { var needsMirrorSupport: Boolean = false /** Will be set to `true` if contains `Quote`. - * The information is used in phase `Staging` in order to avoid traversing trees that need no transformations. + * The information is used in phase `Staging`/`Splicing`/`PickleQuotes` in order to avoid traversing trees that need no transformations. */ var needsStaging: Boolean = false - /** Will be set to `true` if contains `Quote` that needs to be pickled - * The information is used in phase `PickleQuotes` in order to avoid traversing trees that need no transformations. - */ - var needsQuotePickling: Boolean = false - var suspended: Boolean = false var suspendedAtInliningPhase: Boolean = false @@ -115,7 +110,6 @@ object CompilationUnit { val force = new Force force.traverse(unit1.tpdTree) unit1.needsStaging = force.containsQuote - unit1.needsQuotePickling = force.containsQuote unit1.needsInlining = force.containsInline } unit1 diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 44e28bcdd446..d41c57ab116e 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -54,11 +54,13 @@ class Compiler { List(new Inlining) :: // Inline and execute macros List(new PostInlining) :: // Add mirror support for inlined code List(new Staging) :: // Check staging levels and heal staged types + List(new Splicing) :: // Replace level 1 splices with holes List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures Nil /** Phases dealing with the transformation from pickled trees to backend trees */ protected def transformPhases: List[List[Phase]] = + List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage-out is set) List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index de0e78bcc38d..cb117d286a02 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -6,7 +6,7 @@ import core._ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import Symbols._, StdNames._, Trees._, ContextOps._ import Decorators._, transform.SymUtils._ -import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName} +import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, WildcardParamName} import typer.{Namer, Checking} import util.{Property, SourceFile, SourcePosition, Chars} import config.Feature.{sourceVersion, migrateTo3, enabled} @@ -589,12 +589,16 @@ object desugar { // new C[Ts](paramss) lazy val creatorExpr = { - val vparamss = constrVparamss match { - case (vparam :: _) :: _ if vparam.mods.isOneOf(GivenOrImplicit) => // add a leading () to match class parameters + val vparamss = constrVparamss match + case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters Nil :: constrVparamss case _ => - constrVparamss - } + if constrVparamss.nonEmpty && constrVparamss.forall { + case vparam :: _ => vparam.mods.is(Given) + case _ => false + } + then constrVparamss :+ Nil // add a trailing () to match class parameters + else constrVparamss val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) => val app = Apply(nu, vparams.map(refOfDef)) vparams match { @@ -822,7 +826,7 @@ object desugar { } flatTree(cdef1 :: companions ::: implicitWrappers ::: enumScaffolding) - }.showing(i"desugared: $result", Printers.desugar) + }.showing(i"desugared: $cdef --> $result", Printers.desugar) /** Expand * @@ -1109,8 +1113,12 @@ object desugar { * ValDef or DefDef. */ def makePatDef(original: Tree, mods: Modifiers, pat: Tree, rhs: Tree)(using Context): Tree = pat match { - case IdPattern(named, tpt) => - derivedValDef(original, named, tpt, rhs, mods) + case IdPattern(id, tpt) => + val id1 = + if id.name == nme.WILDCARD + then cpy.Ident(id)(WildcardParamName.fresh()) + else id + derivedValDef(original, id1, tpt, rhs, mods) case _ => def filterWildcardGivenBinding(givenPat: Bind): Boolean = @@ -1433,6 +1441,7 @@ object desugar { ValDef(param.name, param.tpt, selector(idx)) .withSpan(param.span) .withAttachment(UntupledParam, ()) + .withFlags(Synthetic) } Function(param :: Nil, Block(vdefs, body)) } @@ -1689,7 +1698,7 @@ object desugar { case (p, n) => makeSyntheticParameter(n + 1, p).withAddedFlags(mods.flags) } RefinedTypeTree(polyFunctionTpt, List( - DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree) + DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree).withFlags(Synthetic) )) } else { diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index ea41984c5766..5e969c0c38c9 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -2,30 +2,40 @@ package dotty.tools.dotc package ast import core._ -import Symbols._, Types._, Contexts._, Flags._, Constants._ -import StdNames.nme - -/** Generate proxy classes for @main functions. - * A function like - * - * @main def f(x: S, ys: T*) = ... - * - * would be translated to something like - * - * import CommandLineParser._ - * class f { - * @static def main(args: Array[String]): Unit = - * try - * f( - * parseArgument[S](args, 0), - * parseRemainingArguments[T](args, 1): _* - * ) - * catch case err: ParseError => showError(err) - * } - */ +import Symbols._, Types._, Contexts._, Decorators._, util.Spans._, Flags._, Constants._ +import StdNames.{nme, tpnme} +import ast.Trees._ +import Names.Name +import Comments.Comment +import NameKinds.DefaultGetterName +import Annotations.Annotation + object MainProxies { - def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + /** Generate proxy classes for @main functions and @myMain functions where myMain <:< MainAnnotation */ + def proxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + mainAnnotationProxies(stats) ++ mainProxies(stats) + } + + /** Generate proxy classes for @main functions. + * A function like + * + * @main def f(x: S, ys: T*) = ... + * + * would be translated to something like + * + * import CommandLineParser._ + * class f { + * @static def main(args: Array[String]): Unit = + * try + * f( + * parseArgument[S](args, 0), + * parseRemainingArguments[T](args, 1): _* + * ) + * catch case err: ParseError => showError(err) + * } + */ + private def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { import tpd._ def mainMethods(stats: List[Tree]): List[Symbol] = stats.flatMap { case stat: DefDef if stat.symbol.hasAnnotation(defn.MainAnnot) => @@ -39,7 +49,7 @@ object MainProxies { } import untpd._ - def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { + private def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { val mainAnnotSpan = mainFun.getAnnotation(defn.MainAnnot).get.tree.span def pos = mainFun.sourcePos val argsRef = Ident(nme.args) @@ -105,13 +115,333 @@ object MainProxies { .filterNot(_.matches(defn.MainAnnot)) .map(annot => insertTypeSplices.transform(annot.tree)) val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) - .withFlags(JavaStatic) + .withFlags(JavaStatic | Synthetic) .withAnnotations(annots) val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) .withFlags(Final | Invisible) - if (!ctx.reporter.hasErrors) result = mainCls.withSpan(mainAnnotSpan.toSynthetic) :: Nil + + if (!ctx.reporter.hasErrors) + result = mainCls.withSpan(mainAnnotSpan.toSynthetic) :: Nil } result } + + private type DefaultValueSymbols = Map[Int, Symbol] + private type ParameterAnnotationss = Seq[Seq[Annotation]] + + /** + * Generate proxy classes for main functions. + * A function like + * + * /** + * * Lorem ipsum dolor sit amet + * * consectetur adipiscing elit. + * * + * * @param x my param x + * * @param ys all my params y + * */ + * @myMain(80) def f( + * @myMain.Alias("myX") x: S, + * y: S, + * ys: T* + * ) = ... + * + * would be translated to something like + * + * final class f { + * static def main(args: Array[String]): Unit = { + * val annotation = new myMain(80) + * val info = new Info( + * name = "f", + * documentation = "Lorem ipsum dolor sit amet consectetur adipiscing elit.", + * parameters = Seq( + * new scala.annotation.MainAnnotation.Parameter("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))), + * new scala.annotation.MainAnnotation.Parameter("y", "S", true, false, "", Seq()), + * new scala.annotation.MainAnnotation.Parameter("ys", "T", false, true, "all my params y", Seq()) + * ) + * ), + * val command = annotation.command(info, args) + * if command.isDefined then + * val cmd = command.get + * val args0: () => S = annotation.argGetter[S](info.parameters(0), cmd(0), None) + * val args1: () => S = annotation.argGetter[S](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) + * val args2: () => Seq[T] = annotation.varargGetter[T](info.parameters(2), cmd.drop(2)) + * annotation.run(() => f(args0(), args1(), args2()*)) + * } + * } + */ + private def mainAnnotationProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + import tpd._ + + /** + * Computes the symbols of the default values of the function. Since they cannot be inferred anymore at this + * point of the compilation, they must be explicitly passed by [[mainProxy]]. + */ + def defaultValueSymbols(scope: Tree, funSymbol: Symbol): DefaultValueSymbols = + scope match { + case TypeDef(_, template: Template) => + template.body.flatMap((_: Tree) match { + case dd: DefDef if dd.name.is(DefaultGetterName) && dd.name.firstPart == funSymbol.name => + val DefaultGetterName.NumberedInfo(index) = dd.name.info + List(index -> dd.symbol) + case _ => Nil + }).toMap + case _ => Map.empty + } + + /** Computes the list of main methods present in the code. */ + def mainMethods(scope: Tree, stats: List[Tree]): List[(Symbol, ParameterAnnotationss, DefaultValueSymbols, Option[Comment])] = stats.flatMap { + case stat: DefDef => + val sym = stat.symbol + sym.annotations.filter(_.matches(defn.MainAnnotationClass)) match { + case Nil => + Nil + case _ :: Nil => + val paramAnnotations = stat.paramss.flatMap(_.map( + valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotationParameterAnnotation)) + )) + (sym, paramAnnotations.toVector, defaultValueSymbols(scope, sym), stat.rawComment) :: Nil + case mainAnnot :: others => + report.error(s"method cannot have multiple main annotations", mainAnnot.tree) + Nil + } + case stat @ TypeDef(_, impl: Template) if stat.symbol.is(Module) => + mainMethods(stat, impl.body) + case _ => + Nil + } + + // Assuming that the top-level object was already generated, all main methods will have a scope + mainMethods(EmptyTree, stats).flatMap(mainAnnotationProxy) + } + + private def mainAnnotationProxy(mainFun: Symbol, paramAnnotations: ParameterAnnotationss, defaultValueSymbols: DefaultValueSymbols, docComment: Option[Comment])(using Context): Option[TypeDef] = { + val mainAnnot = mainFun.getAnnotation(defn.MainAnnotationClass).get + def pos = mainFun.sourcePos + + val documentation = new Documentation(docComment) + + /** () => value */ + def unitToValue(value: Tree): Tree = + val defDef = DefDef(nme.ANON_FUN, List(Nil), TypeTree(), value) + Block(defDef, Closure(Nil, Ident(nme.ANON_FUN), EmptyTree)) + + /** Generate a list of trees containing the ParamInfo instantiations. + * + * A ParamInfo has the following shape + * ``` + * new scala.annotation.MainAnnotation.Parameter("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))) + * ``` + */ + def parameterInfos(mt: MethodType): List[Tree] = + extension (tree: Tree) def withProperty(sym: Symbol, args: List[Tree]) = + Apply(Select(tree, sym.name), args) + + for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield + val param = paramName.toString + val paramType0 = if formal.isRepeatedParam then formal.argTypes.head.dealias else formal.dealias + val paramType = paramType0.dealias + + val paramTypeStr = formal.dealias.typeSymbol.owner.showFullName + "." + paramType.show + val hasDefault = defaultValueSymbols.contains(idx) + val isRepeated = formal.isRepeatedParam + val paramDoc = documentation.argDocs.getOrElse(param, "") + val paramAnnots = + val annotationTrees = paramAnnotations(idx).map(instantiateAnnotation).toList + Apply(ref(defn.SeqModule.termRef), annotationTrees) + + val constructorArgs = List(param, paramTypeStr, hasDefault, isRepeated, paramDoc) + .map(value => Literal(Constant(value))) + + New(TypeTree(defn.MainAnnotationParameter.typeRef), List(constructorArgs :+ paramAnnots)) + + end parameterInfos + + /** + * Creates a list of references and definitions of arguments. + * The goal is to create the + * `val args0: () => S = annotation.argGetter[S](0, cmd(0), None)` + * part of the code. + */ + def argValDefs(mt: MethodType): List[ValDef] = + for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield + val argName = nme.args ++ idx.toString + val isRepeated = formal.isRepeatedParam + val formalType = if isRepeated then formal.argTypes.head else formal + val getterName = if isRepeated then nme.varargGetter else nme.argGetter + val defaultValueGetterOpt = defaultValueSymbols.get(idx) match + case None => ref(defn.NoneModule.termRef) + case Some(dvSym) => + val value = unitToValue(ref(dvSym.termRef)) + Apply(ref(defn.SomeClass.companionModule.termRef), value) + val argGetter0 = TypeApply(Select(Ident(nme.annotation), getterName), TypeTree(formalType) :: Nil) + val index = Literal(Constant(idx)) + val paramInfo = Apply(Select(Ident(nme.info), nme.parameters), index) + val argGetter = + if isRepeated then Apply(argGetter0, List(paramInfo, Apply(Select(Ident(nme.cmd), nme.drop), List(index)))) + else Apply(argGetter0, List(paramInfo, Apply(Ident(nme.cmd), List(index)), defaultValueGetterOpt)) + ValDef(argName, TypeTree(), argGetter) + end argValDefs + + + /** Create a list of argument references that will be passed as argument to the main method. + * `args0`, ...`argn*` + */ + def argRefs(mt: MethodType): List[Tree] = + for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield + val argRef = Apply(Ident(nme.args ++ idx.toString), Nil) + if formal.isRepeatedParam then repeated(argRef) else argRef + end argRefs + + + /** Turns an annotation (e.g. `@main(40)`) into an instance of the class (e.g. `new scala.main(40)`). */ + def instantiateAnnotation(annot: Annotation): Tree = + val argss = { + def recurse(t: tpd.Tree, acc: List[List[Tree]]): List[List[Tree]] = t match { + case Apply(t, args: List[tpd.Tree]) => recurse(t, extractArgs(args) :: acc) + case _ => acc + } + + def extractArgs(args: List[tpd.Tree]): List[Tree] = + args.flatMap { + case Typed(SeqLiteral(varargs, _), _) => varargs.map(arg => TypedSplice(arg)) + case arg: Select if arg.name.is(DefaultGetterName) => Nil // Ignore default values, they will be added later by the compiler + case arg => List(TypedSplice(arg)) + } + + recurse(annot.tree, Nil) + } + + New(TypeTree(annot.symbol.typeRef), argss) + end instantiateAnnotation + + def generateMainClass(mainCall: Tree, args: List[Tree], parameterInfos: List[Tree]): TypeDef = + val cmdInfo = + val nameTree = Literal(Constant(mainFun.showName)) + val docTree = Literal(Constant(documentation.mainDoc)) + val paramInfos = Apply(ref(defn.SeqModule.termRef), parameterInfos) + New(TypeTree(defn.MainAnnotationInfo.typeRef), List(List(nameTree, docTree, paramInfos))) + + val annotVal = ValDef( + nme.annotation, + TypeTree(), + instantiateAnnotation(mainAnnot) + ) + val infoVal = ValDef( + nme.info, + TypeTree(), + cmdInfo + ) + val command = ValDef( + nme.command, + TypeTree(), + Apply( + Select(Ident(nme.annotation), nme.command), + List(Ident(nme.info), Ident(nme.args)) + ) + ) + val argsVal = ValDef( + nme.cmd, + TypeTree(), + Select(Ident(nme.command), nme.get) + ) + val run = Apply(Select(Ident(nme.annotation), nme.run), mainCall) + val body0 = If( + Select(Ident(nme.command), nme.isDefined), + Block(argsVal :: args, run), + EmptyTree + ) + val body = Block(List(annotVal, infoVal, command), body0) // TODO add `if (cmd.nonEmpty)` + + val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) + .withFlags(Param) + /** Replace typed `Ident`s that have been typed with a TypeSplice with the reference to the symbol. + * The annotations will be retype-checked in another scope that may not have the same imports. + */ + def insertTypeSplices = new TreeMap { + override def transform(tree: Tree)(using Context): Tree = tree match + case tree: tpd.Ident @unchecked => TypedSplice(tree) + case tree => super.transform(tree) + } + val annots = mainFun.annotations + .filterNot(_.matches(defn.MainAnnotationClass)) + .map(annot => insertTypeSplices.transform(annot.tree)) + val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) + .withFlags(JavaStatic) + .withAnnotations(annots) + val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) + val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) + .withFlags(Final | Invisible) + mainCls.withSpan(mainAnnot.tree.span.toSynthetic) + end generateMainClass + + if (!mainFun.owner.isStaticOwner) + report.error(s"main method is not statically accessible", pos) + None + else mainFun.info match { + case _: ExprType => + Some(generateMainClass(unitToValue(ref(mainFun.termRef)), Nil, Nil)) + case mt: MethodType => + if (mt.isImplicitMethod) + report.error(s"main method cannot have implicit parameters", pos) + None + else mt.resType match + case restpe: MethodType => + report.error(s"main method cannot be curried", pos) + None + case _ => + Some(generateMainClass(unitToValue(Apply(ref(mainFun.termRef), argRefs(mt))), argValDefs(mt), parameterInfos(mt))) + case _: PolyType => + report.error(s"main method cannot have type parameters", pos) + None + case _ => + report.error(s"main can only annotate a method", pos) + None + } + } + + /** A class responsible for extracting the docstrings of a method. */ + private class Documentation(docComment: Option[Comment]): + import util.CommentParsing._ + + /** The main part of the documentation. */ + lazy val mainDoc: String = _mainDoc + /** The parameters identified by @param. Maps from parameter name to its documentation. */ + lazy val argDocs: Map[String, String] = _argDocs + + private var _mainDoc: String = "" + private var _argDocs: Map[String, String] = Map() + + docComment match { + case Some(comment) => if comment.isDocComment then parseDocComment(comment.raw) else _mainDoc = comment.raw + case None => + } + + private def cleanComment(raw: String): String = + var lines: Seq[String] = raw.trim.nn.split('\n').nn.toSeq + lines = lines.map(l => l.substring(skipLineLead(l, -1), l.length).nn.trim.nn) + var s = lines.foldLeft("") { + case ("", s2) => s2 + case (s1, "") if s1.last == '\n' => s1 // Multiple newlines are kept as single newlines + case (s1, "") => s1 + '\n' + case (s1, s2) if s1.last == '\n' => s1 + s2 + case (s1, s2) => s1 + ' ' + s2 + } + s.replaceAll(raw"\[\[", "").nn.replaceAll(raw"\]\]", "").nn.trim.nn + + private def parseDocComment(raw: String): Unit = + // Positions of the sections (@) in the docstring + val tidx: List[(Int, Int)] = tagIndex(raw) + + // Parse main comment + var mainComment: String = raw.substring(skipLineLead(raw, 0), startTag(raw, tidx)).nn + _mainDoc = cleanComment(mainComment) + + // Parse arguments comments + val argsCommentsSpans: Map[String, (Int, Int)] = paramDocs(raw, "@param", tidx) + val argsCommentsTextSpans = argsCommentsSpans.view.mapValues(extractSectionText(raw, _)) + val argsCommentsTexts = argsCommentsTextSpans.mapValues({ case (beg, end) => raw.substring(beg, end).nn }) + _argDocs = argsCommentsTexts.mapValues(cleanComment(_)).toMap + end Documentation } diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index c393b257af11..2ccd646c3226 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -134,8 +134,11 @@ class TreeTypeMap( val bind1 = tmap.transformSub(bind) val expr1 = tmap.transform(expr) cpy.Labeled(labeled)(bind1, expr1) - case Hole(isTermHole, n, args) => - Hole(isTermHole, n, args.mapConserve(transform)).withSpan(tree.span).withType(mapType(tree.tpe)) + case tree @ Hole(_, _, args, content, tpt) => + val args1 = args.mapConserve(transform) + val content1 = transform(content) + val tpt1 = transform(tpt) + cpy.Hole(tree)(args = args1, content = content1, tpt = tpt1) case lit @ Literal(Constant(tpe: Type)) => cpy.Literal(lit)(Constant(mapType(tpe))) case tree1 => diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index daa1754f7883..c155f42c52e6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -503,6 +503,11 @@ object Trees { def forwardTo: Tree[T] = fun } + object GenericApply: + def unapply[T >: Untyped](tree: Tree[T]): Option[(Tree[T], List[Tree[T]])] = tree match + case tree: GenericApply[T] => Some((tree.fun, tree.args)) + case _ => None + /** The kind of application */ enum ApplyKind: case Regular // r.f(x) @@ -525,8 +530,6 @@ object Trees { attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular) } - - /** fun[args] */ case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends GenericApply[T] { @@ -610,7 +613,7 @@ object Trees { override def toString = s"InlineMatch($selector, $cases)" } - /** case pat if guard => body; only appears as child of a Match */ + /** case pat if guard => body */ case class CaseDef[-T >: Untyped] private[ast] (pat: Tree[T], guard: Tree[T], body: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[-T >: Untyped] = CaseDef[T] @@ -812,7 +815,7 @@ object Trees { case class ValDef[-T >: Untyped] private[ast] (name: TermName, tpt: Tree[T], private var preRhs: LazyTree[T @uncheckedVariance])(implicit @constructorOnly src: SourceFile) extends ValOrDefDef[T], ValOrTypeDef[T] { type ThisTree[-T >: Untyped] = ValDef[T] - assert(isEmpty || tpt != genericEmptyTree) + assert(isEmpty || (tpt ne genericEmptyTree)) def unforced: LazyTree[T] = preRhs protected def force(x: Tree[T @uncheckedVariance]): Unit = preRhs = x } @@ -822,7 +825,7 @@ object Trees { paramss: List[ParamClause[T]], tpt: Tree[T], private var preRhs: LazyTree[T @uncheckedVariance])(implicit @constructorOnly src: SourceFile) extends ValOrDefDef[T] { type ThisTree[-T >: Untyped] = DefDef[T] - assert(tpt != genericEmptyTree) + assert(tpt ne genericEmptyTree) def unforced: LazyTree[T] = preRhs protected def force(x: Tree[T @uncheckedVariance]): Unit = preRhs = x @@ -972,10 +975,16 @@ object Trees { def genericEmptyValDef[T >: Untyped]: ValDef[T] = theEmptyValDef.asInstanceOf[ValDef[T]] def genericEmptyTree[T >: Untyped]: Thicket[T] = theEmptyTree.asInstanceOf[Thicket[T]] - /** Tree that replaces a splice in pickled quotes. - * It is only used when picking quotes (Will never be in a TASTy file). + /** Tree that replaces a level 1 splices in pickled (level 0) quotes. + * It is only used when picking quotes (will never be in a TASTy file). + * + * @param isTermHole If this hole is a term, otherwise it is a type hole. + * @param idx The index of the hole in it's enclosing level 0 quote. + * @param args The arguments of the splice to compute its content + * @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle. + * @param tpt Type of the hole */ - case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends Tree[T] { + case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[-T >: Untyped] <: Hole[T] override def isTerm: Boolean = isTermHole override def isType: Boolean = !isTermHole @@ -1331,6 +1340,10 @@ object Trees { case tree: Thicket if (trees eq tree.trees) => tree case _ => finalize(tree, untpd.Thicket(trees)(sourceFile(tree))) } + def Hole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match { + case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && content.eq(tree.content) && content.eq(tree.content) => tree + case _ => finalize(tree, untpd.Hole(isTerm, idx, args, content, tpt)(sourceFile(tree))) + } // Copier methods with default arguments; these demand that the original tree // is of the same class as the copy. We only include trees with more than 2 elements here. @@ -1352,6 +1365,9 @@ object Trees { TypeDef(tree: Tree)(name, rhs) def Template(tree: Template)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, derived: List[untpd.Tree] = tree.derived, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody)(using Context): Template = Template(tree: Tree)(constr, parents, derived, self, body) + def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole = + Hole(tree: Tree)(isTerm, idx, args, content, tpt) + } /** Hook to indicate that a transform of some subtree should be skipped */ @@ -1367,13 +1383,26 @@ object Trees { /** The context to use when mapping or accumulating over a tree */ def localCtx(tree: Tree)(using Context): Context + /** The context to use when transforming a tree. + * It ensures that the source is correct, and that the local context is used if + * that's necessary for transforming the whole tree. + * TODO: ensure transform is always called with the correct context as argument + * @see https://github.com/lampepfl/dotty/pull/13880#discussion_r836395977 + */ + def transformCtx(tree: Tree)(using Context): Context = + val sourced = + if tree.source.exists && tree.source != ctx.source + then ctx.withSource(tree.source) + else ctx + tree match + case t: (MemberDef | PackageDef | LambdaTypeTree | TermLambdaTypeTree) => + localCtx(t)(using sourced) + case _ => + sourced + abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(using Context): Tree = { - inContext( - if tree.source != ctx.source && tree.source.exists - then ctx.withSource(tree.source) - else ctx - ){ + inContext(transformCtx(tree)) { Stats.record(s"TreeMap.transform/$getClass") if (skipTransform(tree)) tree else tree match { @@ -1430,13 +1459,9 @@ object Trees { case AppliedTypeTree(tpt, args) => cpy.AppliedTypeTree(tree)(transform(tpt), transform(args)) case LambdaTypeTree(tparams, body) => - inContext(localCtx(tree)) { - cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) - } + cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) case TermLambdaTypeTree(params, body) => - inContext(localCtx(tree)) { - cpy.TermLambdaTypeTree(tree)(transformSub(params), transform(body)) - } + cpy.TermLambdaTypeTree(tree)(transformSub(params), transform(body)) case MatchTypeTree(bound, selector, cases) => cpy.MatchTypeTree(tree)(transform(bound), transform(selector), transformSub(cases)) case ByNameTypeTree(result) => @@ -1452,19 +1477,13 @@ object Trees { case EmptyValDef => tree case tree @ ValDef(name, tpt, _) => - inContext(localCtx(tree)) { - val tpt1 = transform(tpt) - val rhs1 = transform(tree.rhs) - cpy.ValDef(tree)(name, tpt1, rhs1) - } + val tpt1 = transform(tpt) + val rhs1 = transform(tree.rhs) + cpy.ValDef(tree)(name, tpt1, rhs1) case tree @ DefDef(name, paramss, tpt, _) => - inContext(localCtx(tree)) { - cpy.DefDef(tree)(name, transformParamss(paramss), transform(tpt), transform(tree.rhs)) - } + cpy.DefDef(tree)(name, transformParamss(paramss), transform(tpt), transform(tree.rhs)) case tree @ TypeDef(name, rhs) => - inContext(localCtx(tree)) { - cpy.TypeDef(tree)(name, transform(rhs)) - } + cpy.TypeDef(tree)(name, transform(rhs)) case tree @ Template(constr, parents, self, _) if tree.derived.isEmpty => cpy.Template(tree)(transformSub(constr), transform(tree.parents), Nil, transformSub(self), transformStats(tree.body, tree.symbol)) case Import(expr, selectors) => @@ -1472,15 +1491,14 @@ object Trees { case Export(expr, selectors) => cpy.Export(tree)(transform(expr), selectors) case PackageDef(pid, stats) => - val pid1 = transformSub(pid) - inContext(localCtx(tree)) { - cpy.PackageDef(tree)(pid1, transformStats(stats, ctx.owner)) - } + cpy.PackageDef(tree)(transformSub(pid), transformStats(stats, ctx.owner)) case Annotated(arg, annot) => cpy.Annotated(tree)(transform(arg), transform(annot)) case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) + case tree @ Hole(_, _, args, content, tpt) => + cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt)) case _ => transformMoreCases(tree) } @@ -1620,8 +1638,8 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case Hole(_, _, args) => - this(x, args) + case Hole(_, _, args, content, tpt) => + this(this(this(x, args), content), tpt) case _ => foldMoreCases(x, tree) } @@ -1639,6 +1657,7 @@ object Trees { abstract class TreeTraverser extends TreeAccumulator[Unit] { def traverse(tree: Tree)(using Context): Unit + def traverse(trees: List[Tree])(using Context) = apply((), trees) def apply(x: Unit, tree: Tree)(using Context): Unit = traverse(tree) protected def traverseChildren(tree: Tree)(using Context): Unit = foldOver((), tree) } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index fb8bd917ca20..2e87194ff207 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -377,6 +377,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Throw(expr: Tree)(using Context): Tree = ref(defn.throwMethod).appliedTo(expr) + def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = + ta.assignType(untpd.Hole(isTermHole, idx, args, content, tpt), tpt) + // ------ Making references ------------------------------------------------------ def prefixIsElidable(tp: NamedType)(using Context): Boolean = { @@ -1518,10 +1521,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * @param tpe the type of the elements of the resulting list. * */ - def mkList(trees: List[Tree], tpe: Tree)(using Context): Tree = + def mkList(trees: List[Tree], tpt: Tree)(using Context): Tree = ref(defn.ListModule).select(nme.apply) - .appliedToTypeTree(tpe) - .appliedToVarargs(trees, tpe) + .appliedToTypeTree(tpt) + .appliedToVarargs(trees, tpt) protected def FunProto(args: List[Tree], resType: Type)(using Context) = diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index de95526c5991..365b969faffc 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -411,6 +411,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Export(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Export = new Export(expr, selectors) def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats) def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot) + def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, args, content, tpt) // ------ Additional creation methods for untyped only ----------------- diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index f9c49c1a7723..afa30e38dc2a 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -131,7 +131,9 @@ object PathResolver { def fromPathString(path: String)(using Context): ClassPath = { val settings = ctx.settings.classpath.update(path) - new PathResolver()(using ctx.fresh.setSettings(settings)).result + inContext(ctx.fresh.setSettings(settings)) { + new PathResolver().result + } } /** Show values in Environment and Defaults when no argument is provided. @@ -147,7 +149,9 @@ object PathResolver { val ArgsSummary(sstate, rest, errors, warnings) = ctx.settings.processArguments(args.toList, true, ctx.settingsState) errors.foreach(println) - val pr = new PathResolver()(using ctx.fresh.setSettings(sstate)) + val pr = inContext(ctx.fresh.setSettings(sstate)) { + new PathResolver() + } println(" COMMAND: 'scala %s'".format(args.mkString(" "))) println("RESIDUAL: 'scala %s'\n".format(rest.mkString(" "))) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8b163acb1fa9..20cbcd136fee 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -16,7 +16,7 @@ class ScalaSettings extends SettingGroup with AllScalaSettings object ScalaSettings: // Keep synchronized with `classfileVersion` in `BCodeIdiomatic` private val minTargetVersion = 8 - private val maxTargetVersion = 18 + private val maxTargetVersion = 19 def supportedTargetVersions: List[String] = (minTargetVersion to maxTargetVersion).toList.map(_.toString) @@ -117,6 +117,9 @@ trait CommonScalaSettings: val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language")) + /* Coverage settings */ + val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out")) + /* Other settings */ val encoding: Setting[String] = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) val usejavacp: Setting[Boolean] = BooleanSetting("-usejavacp", "Utilize the java.class.path in classpath resolution.", aliases = List("--use-java-class-path")) diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index 9361c7e6774d..4c61ddd036ae 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -17,5 +17,12 @@ enum SourceVersion: object SourceVersion extends Property.Key[SourceVersion]: - val allSourceVersionNames = values.toList.map(_.toString.toTermName) + /** language versions that may appear in a language import, are deprecated, but not removed from the standard library. */ + val illegalSourceVersionNames = List("3.1-migration").map(_.toTermName) + + /** language versions that the compiler recognises. */ + val validSourceVersionNames = values.toList.map(_.toString.toTermName) + + /** All source versions that can be recognised from a language import. e.g. `import language.3.1` */ + val allSourceVersionNames = validSourceVersionNames ::: illegalSourceVersionNames end SourceVersion diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 08452782fd66..ce488ef23885 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -2,12 +2,13 @@ package dotty.tools package dotc package core -import annotation.tailrec -import Symbols._ -import Contexts._, Names._, Phases._, printing.Texts._ -import collection.mutable.ListBuffer -import dotty.tools.dotc.transform.MegaPhase -import printing.Formatting._ +import scala.annotation.tailrec +import scala.collection.mutable.ListBuffer +import scala.util.control.NonFatal + +import Contexts._, Names._, Phases._, Symbols._ +import printing.{ Printer, Showable }, printing.Formatting._, printing.Texts._ +import transform.MegaPhase /** This object provides useful implicit decorators for types defined elsewhere */ object Decorators { @@ -246,13 +247,29 @@ object Decorators { } extension [T](x: T) - def showing( - op: WrappedResult[T] ?=> String, - printer: config.Printers.Printer = config.Printers.default): T = { - printer.println(op(using WrappedResult(x))) + def showing[U]( + op: WrappedResult[U] ?=> String, + printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] | Null = null): T = { + // either the use of `$result` was driven by the expected type of `Shown` + // which led to the summoning of `Conversion[T, Shown]` (which we'll invoke) + // or no such conversion was found so we'll consume the result as it is instead + val obj = if c == null then x.asInstanceOf[U] else c(x) + printer.println(op(using WrappedResult(obj))) x } + /** Instead of `toString` call `show` on `Showable` values, falling back to `toString` if an exception is raised. */ + def tryToShow(using Context): String = x match + case x: Showable => + try x.show + catch + case ex: CyclicReference => "... (caught cyclic reference) ..." + case NonFatal(ex) + if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value => + val msg = ex match { case te: TypeError => te.toMessage case _ => ex.getMessage } + s"[cannot display due to $msg, raw string = $x]" + case _ => String.valueOf(x).nn + extension [T](x: T) def assertingErrorsReported(using Context): T = { assert(ctx.reporter.errorsReported) @@ -269,19 +286,19 @@ object Decorators { extension (sc: StringContext) /** General purpose string formatting */ - def i(args: Any*)(using Context): String = + def i(args: Shown*)(using Context): String = new StringFormatter(sc).assemble(args) /** Formatting for error messages: Like `i` but suppress follow-on * error messages after the first one if some of their arguments are "non-sensical". */ - def em(args: Any*)(using Context): String = + def em(args: Shown*)(using Context): String = new ErrorMessageFormatter(sc).assemble(args) /** Formatting with added explanations: Like `em`, but add explanations to * give more info about type variables and to disambiguate where needed. */ - def ex(args: Any*)(using Context): String = + def ex(args: Shown*)(using Context): String = explained(em(args: _*)) extension [T <: AnyRef](arr: Array[T]) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index dc672690702c..e09b7ca98955 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -13,6 +13,7 @@ import typer.ImportInfo.RootRef import Comments.CommentsContext import Comments.Comment import util.Spans.NoSpan +import Symbols.requiredModuleRef import scala.annotation.tailrec @@ -460,6 +461,9 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef + @tu lazy val InvokerModule = requiredModule("scala.runtime.coverage.Invoker") + @tu lazy val InvokedMethodRef = InvokerModule.requiredMethodRef("invoked") + @tu lazy val ImplicitScrutineeTypeSym = newPermanentSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef @@ -524,6 +528,8 @@ class Definitions { @tu lazy val Seq_lengthCompare: Symbol = SeqClass.requiredMethod(nme.lengthCompare, List(IntType)) @tu lazy val Seq_length : Symbol = SeqClass.requiredMethod(nme.length) @tu lazy val Seq_toSeq : Symbol = SeqClass.requiredMethod(nme.toSeq) + @tu lazy val SeqModule: Symbol = requiredModule("scala.collection.immutable.Seq") + @tu lazy val StringOps: Symbol = requiredClass("scala.collection.StringOps") @tu lazy val StringOps_format: Symbol = StringOps.requiredMethod(nme.format) @@ -785,10 +791,44 @@ class Definitions { @tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr") @tu lazy val QuotesClass: ClassSymbol = requiredClass("scala.quoted.Quotes") + @tu lazy val Quotes_reflect: Symbol = QuotesClass.requiredValue("reflect") + @tu lazy val Quotes_reflect_asTerm: Symbol = Quotes_reflect.requiredMethod("asTerm") + @tu lazy val Quotes_reflect_Apply: Symbol = Quotes_reflect.requiredValue("Apply") + @tu lazy val Quotes_reflect_Apply_apply: Symbol = Quotes_reflect_Apply.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_TypeApply: Symbol = Quotes_reflect.requiredValue("TypeApply") + @tu lazy val Quotes_reflect_TypeApply_apply: Symbol = Quotes_reflect_TypeApply.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Assign: Symbol = Quotes_reflect.requiredValue("Assign") + @tu lazy val Quotes_reflect_Assign_apply: Symbol = Quotes_reflect_Assign.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Inferred: Symbol = Quotes_reflect.requiredValue("Inferred") + @tu lazy val Quotes_reflect_Inferred_apply: Symbol = Quotes_reflect_Inferred.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Literal: Symbol = Quotes_reflect.requiredValue("Literal") + @tu lazy val Quotes_reflect_Literal_apply: Symbol = Quotes_reflect_Literal.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_TreeMethods: Symbol = Quotes_reflect.requiredMethod("TreeMethods") + @tu lazy val Quotes_reflect_TreeMethods_asExpr: Symbol = Quotes_reflect_TreeMethods.requiredMethod("asExpr") + @tu lazy val Quotes_reflect_TypeRepr: Symbol = Quotes_reflect.requiredValue("TypeRepr") + @tu lazy val Quotes_reflect_TypeRepr_of: Symbol = Quotes_reflect_TypeRepr.requiredMethod("of") + @tu lazy val Quotes_reflect_TypeRepr_typeConstructorOf: Symbol = Quotes_reflect_TypeRepr.requiredMethod("typeConstructorOf") + @tu lazy val Quotes_reflect_TypeReprMethods: Symbol = Quotes_reflect.requiredValue("TypeReprMethods") + @tu lazy val Quotes_reflect_TypeReprMethods_asType: Symbol = Quotes_reflect_TypeReprMethods.requiredMethod("asType") + @tu lazy val Quotes_reflect_TypeTreeType: Symbol = Quotes_reflect.requiredType("TypeTree") + @tu lazy val Quotes_reflect_TermType: Symbol = Quotes_reflect.requiredType("Term") + @tu lazy val Quotes_reflect_BooleanConstant: Symbol = Quotes_reflect.requiredValue("BooleanConstant") + @tu lazy val Quotes_reflect_ByteConstant: Symbol = Quotes_reflect.requiredValue("ByteConstant") + @tu lazy val Quotes_reflect_ShortConstant: Symbol = Quotes_reflect.requiredValue("ShortConstant") + @tu lazy val Quotes_reflect_IntConstant: Symbol = Quotes_reflect.requiredValue("IntConstant") + @tu lazy val Quotes_reflect_LongConstant: Symbol = Quotes_reflect.requiredValue("LongConstant") + @tu lazy val Quotes_reflect_FloatConstant: Symbol = Quotes_reflect.requiredValue("FloatConstant") + @tu lazy val Quotes_reflect_DoubleConstant: Symbol = Quotes_reflect.requiredValue("DoubleConstant") + @tu lazy val Quotes_reflect_CharConstant: Symbol = Quotes_reflect.requiredValue("CharConstant") + @tu lazy val Quotes_reflect_StringConstant: Symbol = Quotes_reflect.requiredValue("StringConstant") + @tu lazy val Quotes_reflect_UnitConstant: Symbol = Quotes_reflect.requiredValue("UnitConstant") + @tu lazy val Quotes_reflect_NullConstant: Symbol = Quotes_reflect.requiredValue("NullConstant") + @tu lazy val Quotes_reflect_ClassOfConstant: Symbol = Quotes_reflect.requiredValue("ClassOfConstant") + @tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler") - @tu lazy val QuoteUnpickler_unpickleExpr: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExpr") - @tu lazy val QuoteUnpickler_unpickleType: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleType") + @tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2") + @tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2") @tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching") @tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch") @@ -849,6 +889,12 @@ class Definitions { @tu lazy val XMLTopScopeModule: Symbol = requiredModule("scala.xml.TopScope") + @tu lazy val MainAnnotationClass: ClassSymbol = requiredClass("scala.annotation.MainAnnotation") + @tu lazy val MainAnnotationInfo: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Info") + @tu lazy val MainAnnotationParameter: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Parameter") + @tu lazy val MainAnnotationParameterAnnotation: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.ParameterAnnotation") + @tu lazy val MainAnnotationCommand: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Command") + @tu lazy val CommandLineParserModule: Symbol = requiredModule("scala.util.CommandLineParser") @tu lazy val CLP_ParseError: ClassSymbol = CommandLineParserModule.requiredClass("ParseError").typeRef.symbol.asClass @tu lazy val CLP_parseArgument: Symbol = CommandLineParserModule.requiredMethod("parseArgument") @@ -1991,7 +2037,7 @@ class Definitions { add(MatchableClass, """/** The base trait of types that can be safely pattern matched against. | * - | * See [[https://dotty.epfl.ch/docs/reference/other-new-features/matchable.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/other-new-features/matchable.html]]. | */ """.stripMargin) @@ -2100,21 +2146,21 @@ class Definitions { add(AnyKindClass, """/** The super-type of all types. | * - | * See [[https://dotty.epfl.ch/docs/reference/other-new-features/kind-polymorphism.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/other-new-features/kind-polymorphism.html]]. | */ """.stripMargin) add(andType, """/** The intersection of two types. | * - | * See [[https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/new-types/intersection-types.html]]. | */ """.stripMargin) add(orType, """/** The union of two types. | * - | * See [[https://dotty.epfl.ch/docs/reference/new-types/union-types.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/new-types/union-types.html]]. | */ """.stripMargin) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 08dd47af56c2..a19f35a9cd9a 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -361,5 +361,14 @@ object NameOps { case raw.BANG => UNARY_! case _ => name } + + /** If this is a super accessor name, its underlying name, which is the name + * of the method that the super accessor forwards to. + */ + def originalOfSuperAccessorName: TermName = name match + case SuperAccessorName(name1) => name1.originalOfSuperAccessorName + case ExpandedName(_, name1) => name1.originalOfSuperAccessorName + case ExpandPrefixName(_, name1) => name1.originalOfSuperAccessorName + case _ => name } } diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 56022e9fcaf2..1a878c9547b1 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -18,14 +18,23 @@ object NamerOps: case TypeSymbols(tparams) :: _ => ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef)) case _ => ctor.owner.typeRef - /** if isConstructor, make sure it has one leading non-implicit parameter list */ + /** If isConstructor, make sure it has at least one non-implicit parameter list + * This is done by adding a () in front of a leading old style implicit parameter, + * or by adding a () as last -- or only -- parameter list if the constructor has + * only using clauses as parameters. + */ def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean)(using Context): List[List[Symbol]] = if !isConstructor then paramss else paramss match - case Nil :: _ => paramss - case TermSymbols(vparam :: _) :: _ if !vparam.isOneOf(GivenOrImplicit) => paramss case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor) - case _ => Nil :: paramss + case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => Nil :: paramss + case _ => + if paramss.forall { + case TermSymbols(vparams) => vparams.nonEmpty && vparams.head.is(Given) + case _ => true + } + then paramss :+ Nil + else paramss /** The method type corresponding to given parameters and result type */ def methodType(paramss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(using Context): Type = diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index e43f617bf965..b72515b518a6 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -201,7 +201,7 @@ object Phases { private var mySbtExtractDependenciesPhase: Phase = _ private var myPicklerPhase: Phase = _ private var myInliningPhase: Phase = _ - private var myPickleQuotesPhase: Phase = _ + private var mySplicingPhase: Phase = _ private var myFirstTransformPhase: Phase = _ private var myCollectNullableFieldsPhase: Phase = _ private var myRefChecksPhase: Phase = _ @@ -224,7 +224,7 @@ object Phases { final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase final def inliningPhase: Phase = myInliningPhase - final def pickleQuotesPhase: Phase = myPickleQuotesPhase + final def splicingPhase: Phase = mySplicingPhase final def firstTransformPhase: Phase = myFirstTransformPhase final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase final def refchecksPhase: Phase = myRefChecksPhase @@ -250,7 +250,7 @@ object Phases { mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myInliningPhase = phaseOfClass(classOf[Inlining]) - myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes]) + mySplicingPhase = phaseOfClass(classOf[Splicing]) myFirstTransformPhase = phaseOfClass(classOf[FirstTransform]) myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) myRefChecksPhase = phaseOfClass(classOf[RefChecks]) @@ -426,7 +426,7 @@ object Phases { def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase def inliningPhase(using Context): Phase = ctx.base.inliningPhase - def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase + def splicingPhase(using Context): Phase = ctx.base.splicingPhase def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase diff --git a/compiler/src/dotty/tools/dotc/core/StagingContext.scala b/compiler/src/dotty/tools/dotc/core/StagingContext.scala index 4a4f451df099..41e77655d5d6 100644 --- a/compiler/src/dotty/tools/dotc/core/StagingContext.scala +++ b/compiler/src/dotty/tools/dotc/core/StagingContext.scala @@ -8,11 +8,11 @@ import dotty.tools.dotc.transform.PCPCheckAndHeal object StagingContext { - /** A key to be used in a context property that tracks the quoteation level */ + /** A key to be used in a context property that tracks the quotation level */ private val QuotationLevel = new Property.Key[Int] - /** A key to be used in a context property that tracks the quoteation stack. - * Stack containing the Quotes references recieved by the surrounding quotes. + /** A key to be used in a context property that tracks the quotation stack. + * Stack containing the Quotes references received by the surrounding quotes. */ private val QuotesStack = new Property.Key[List[tpd.Tree]] @@ -26,7 +26,7 @@ object StagingContext { def quoteContext(using Context): Context = ctx.fresh.setProperty(QuotationLevel, level + 1) - /** Context with an incremented quotation level and pushes a refecence to a Quotes on the quote context stack */ + /** Context with an incremented quotation level and pushes a reference to a Quotes on the quote context stack */ def pushQuotes(qctxRef: tpd.Tree)(using Context): Context = val old = ctx.property(QuotesStack).getOrElse(List.empty) ctx.fresh.setProperty(QuotationLevel, level + 1) @@ -43,7 +43,7 @@ object StagingContext { ctx.property(TaggedTypes).get /** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty. - * The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice. + * The quotation stack could be empty if we are in a top level splice or an erroneous splice directly within a top level splice. */ def popQuotes()(using Context): (Option[tpd.Tree], Context) = val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 1bf91bf69abe..949a99c1e016 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -397,6 +397,7 @@ object StdNames { val applyOrElse: N = "applyOrElse" val args : N = "args" val argv : N = "argv" + val argGetter : N = "argGetter" val arrayClass: N = "arrayClass" val arrayElementClass: N = "arrayElementClass" val arrayType: N = "arrayType" @@ -427,9 +428,12 @@ object StdNames { val classOf: N = "classOf" val classType: N = "classType" val clone_ : N = "clone" + val cmd: N = "cmd" + val command: N = "command" val common: N = "common" val compiletime : N = "compiletime" val conforms_ : N = "$conforms" + val contents: N = "contents" val copy: N = "copy" val currentMirror: N = "currentMirror" val create: N = "create" @@ -483,6 +487,7 @@ object StdNames { val hash_ : N = "hash" val head: N = "head" val higherKinds: N = "higherKinds" + val idx: N = "idx" val identity: N = "identity" val implicitConversions: N = "implicitConversions" val implicitly: N = "implicitly" @@ -540,6 +545,7 @@ object StdNames { val ordinalDollar: N = "$ordinal" val ordinalDollar_ : N = "_$ordinal" val origin: N = "origin" + val parameters: N = "parameters" val parts: N = "parts" val postfixOps: N = "postfixOps" val prefix : N = "prefix" @@ -549,6 +555,7 @@ object StdNames { val productElementName: N = "productElementName" val productIterator: N = "productIterator" val productPrefix: N = "productPrefix" + val quotes : N = "quotes" val raw_ : N = "raw" val refl: N = "refl" val reflect: N = "reflect" @@ -613,6 +620,7 @@ object StdNames { val fromOrdinal: N = "fromOrdinal" val values: N = "values" val view_ : N = "view" + val varargGetter : N = "varargGetter" val wait_ : N = "wait" val wildcardType: N = "wildcardType" val withFilter: N = "withFilter" diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 143b71d85b5b..e1605c326a05 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2730,15 +2730,16 @@ object TypeComparer { */ val Fresh: Repr = 4 - extension (approx: Repr) - def low: Boolean = (approx & LoApprox) != 0 - def high: Boolean = (approx & HiApprox) != 0 - def addLow: Repr = approx | LoApprox - def addHigh: Repr = approx | HiApprox - def show: String = - val lo = if low then " (left is approximated)" else "" - val hi = if high then " (right is approximated)" else "" - lo ++ hi + object Repr: + extension (approx: Repr) + def low: Boolean = (approx & LoApprox) != 0 + def high: Boolean = (approx & HiApprox) != 0 + def addLow: Repr = approx | LoApprox + def addHigh: Repr = approx | HiApprox + def show: String = + val lo = if low then " (left is approximated)" else "" + val hi = if high then " (right is approximated)" else "" + lo ++ hi end ApproxState type ApproxState = ApproxState.Repr diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e6ab80afaba5..9ab49d2f2062 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1548,9 +1548,10 @@ object Types { @tailrec def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => pre.refinedInfo match { - case TypeAlias(alias) => - if (pre.refinedName ne name) loop(pre.parent) else alias - case _ => loop(pre.parent) + case tp: AliasingBounds => + if (pre.refinedName ne name) loop(pre.parent) else tp.alias + case _ => + loop(pre.parent) } case pre: RecType => val candidate = pre.parent.lookupRefined(name) @@ -4209,7 +4210,7 @@ object Types { * This is the case if tycon is higher-kinded. This means * it is a subtype of a hk-lambda, but not a match alias. * (normal parameterized aliases are removed in `appliedTo`). - * Applications of hgher-kinded type constructors to wildcard arguments + * Applications of higher-kinded type constructors to wildcard arguments * are equivalent to existential types, which are not supported. */ def isUnreducibleWild(using Context): Boolean = @@ -4640,7 +4641,7 @@ object Types { myRepr.nn } - override def toString: String = s"Skolem($hashCode)" + override def toString: String = s"SkolemType($hashCode)" } /** A skolem type used to wrap the type of the qualifier of a selection. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index dd2cc46c406d..09aae581fcfc 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -637,11 +637,11 @@ class TreePickler(pickler: TastyPickler) { pickleTree(hi) pickleTree(alias) } - case Hole(_, idx, args) => + case Hole(_, idx, args, _, tpt) => writeByte(HOLE) withLength { writeNat(idx) - pickleType(tree.tpe, richTypes = true) + pickleType(tpt.tpe, richTypes = true) args.foreach(pickleTree) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index aea6a4140e5a..0bb4ebd9f772 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -27,7 +27,7 @@ import typer.ConstFold import typer.Checking.checkNonCyclic import typer.Nullables._ import util.Spans._ -import util.SourceFile +import util.{SourceFile, Property} import ast.{Trees, tpd, untpd} import Trees._ import Decorators._ @@ -515,16 +515,28 @@ class TreeUnpickler(reader: TastyReader, flags } - def isAbstractType(ttag: Int)(using Context): Boolean = nextUnsharedTag match { + def isAbstractType(name: Name)(using Context): Boolean = nextByte match + case SHAREDtype => + val lookAhead = fork + lookAhead.reader.readByte() + val sharedReader = forkAt(lookAhead.reader.readAddr()) + sharedReader.isAbstractType(name) case LAMBDAtpt => val rdr = fork rdr.reader.readByte() // tag rdr.reader.readNat() // length rdr.skipParams() // tparams - rdr.isAbstractType(rdr.nextUnsharedTag) - case TYPEBOUNDS | TYPEBOUNDStpt => true + rdr.isAbstractType(name) + case TYPEBOUNDS => + val rdr = fork + rdr.reader.readByte() // tag + val end = rdr.reader.readEnd() + rdr.skipTree() // alias, or lower bound + val res = !rdr.nothingButMods(end) + //if !res then println(i"NOT ABSTRACT $name, ${rdr.reader.nextByte}") + res + case TYPEBOUNDStpt => true case _ => false - } /** Create symbol of definition node and enter in symAtAddr map * @return the created symbol @@ -569,7 +581,7 @@ class TreeUnpickler(reader: TastyReader, if (tag == TYPEDEF || tag == TYPEPARAM) name = name.toTypeName skipParams() val ttag = nextUnsharedTag - val isAbsType = isAbstractType(ttag) + val isAbsType = isAbstractType(name) val isClass = ttag == TEMPLATE val templateStart = currentAddr skipTree() // tpt @@ -577,7 +589,7 @@ class TreeUnpickler(reader: TastyReader, val rhsIsEmpty = nothingButMods(end) if (!rhsIsEmpty) skipTree() val (givenFlags, annotFns, privateWithin) = readModifiers(end) - pickling.println(i"creating symbol $name at $start with flags $givenFlags") + pickling.println(i"creating symbol $name at $start with flags ${givenFlags.flagsString}, isAbsType = $isAbsType, $ttag") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = if (flags.is(Module)) adjustModuleCompleter(completer, name) else completer @@ -1047,14 +1059,25 @@ class TreeUnpickler(reader: TastyReader, else Nil - def readIndexedStats(exprOwner: Symbol, end: Addr)(using Context): List[Tree] = - until(end)(readIndexedStat(exprOwner)) + def readIndexedStats[T](exprOwner: Symbol, end: Addr, k: (List[Tree], Context) => T = sameTrees)(using Context): T = + val buf = new mutable.ListBuffer[Tree] + var curCtx = ctx + while currentAddr.index < end.index do + val stat = readIndexedStat(exprOwner)(using curCtx) + buf += stat + stat match + case stat: Import => curCtx = ctx.importContext(stat, stat.symbol) + case _ => + assert(currentAddr.index == end.index) + k(buf.toList, curCtx) - def readStats(exprOwner: Symbol, end: Addr)(using Context): List[Tree] = { + def readStats[T](exprOwner: Symbol, end: Addr, k: (List[Tree], Context) => T = sameTrees)(using Context): T = { fork.indexStats(end) - readIndexedStats(exprOwner, end) + readIndexedStats(exprOwner, end, k) } + private def sameTrees(xs: List[Tree], ctx: Context) = xs + def readIndexedParams[T <: MemberDef](tag: Int)(using Context): List[T] = collectWhile(nextByte == tag) { readIndexedDef().asInstanceOf[T] } @@ -1140,6 +1163,36 @@ class TreeUnpickler(reader: TastyReader, readPathTerm() } + /** Adapt constructor calls where class has only using clauses from old to new scheme. + * or class has mixed using clauses and other clauses. + * Old: leading (), new: nothing, or trailing () if all clauses are using clauses. + * This is neccessary so that we can read pre-3.2 Tasty correctly. There, + * constructor calls use the old scheme, but constructor definitions already + * use the new scheme, since they are reconstituted with normalizeIfConstructor. + */ + def constructorApply(fn: Tree, args: List[Tree]): Tree = + if fn.tpe.widen.isContextualMethod && args.isEmpty then + fn.withAttachment(SuppressedApplyToNone, ()) + else + val fn1 = fn match + case Apply(fn1, Nil) if fn.removeAttachment(InsertedApplyToNone).isDefined => + // We thought we inserted a final `()` but hit a user-written `()` instead. + // Remove the inserted `()`. + fn1 + case _ => + fn + val res = tpd.Apply(fn1, args) + if fn.removeAttachment(SuppressedApplyToNone).isEmpty then + res + else res.tpe.widen match + case mt @ MethodType(params) => + if params.isEmpty then + // Assume it's the final synthesized `()` parameter + res.appliedToNone.withAttachment(InsertedApplyToNone, ()) + else if mt.isContextualMethod then + res.withAttachment(SuppressedApplyToNone, ()) + else res + def readLengthTerm(): Tree = { val end = readEnd() val result = @@ -1150,7 +1203,9 @@ class TreeUnpickler(reader: TastyReader, tpd.Super(qual, mixId, mixTpe.typeSymbol) case APPLY => val fn = readTerm() - tpd.Apply(fn, until(end)(readTerm())) + val args = until(end)(readTerm()) + if fn.symbol.isConstructor then constructorApply(fn, args) + else tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) case TYPED => @@ -1162,9 +1217,8 @@ class TreeUnpickler(reader: TastyReader, case BLOCK => val exprReader = fork skipTree() - val stats = readStats(ctx.owner, end) - val expr = exprReader.readTerm() - Block(stats, expr) + readStats(ctx.owner, end, + (stats, ctx) => Block(stats, exprReader.readTerm()(using ctx))) case INLINED => val exprReader = fork skipTree() @@ -1301,7 +1355,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - Hole(true, idx, args).withType(tpe) + Hole(true, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => readPathTerm() } @@ -1335,7 +1389,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - Hole(false, idx, args).withType(tpe) + Hole(false, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => if (isTypeTreeTag(nextByte)) readTerm() else { @@ -1538,4 +1592,14 @@ object TreeUnpickler { inline val AllDefs = 2 // add everything class TreeWithoutOwner extends Exception + + /** An attachment key indicating that an old-style leading () in a constructor + * call that is followed by a using clause was suppressed. + */ + val SuppressedApplyToNone: Property.Key[Unit] = Property.Key() + + /** An attachment key indicating that a trailing () in a constructor + * call that has otherwise only using clauses was inserted. + */ + val InsertedApplyToNone: Property.Key[Unit] = Property.Key() } diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala new file mode 100644 index 000000000000..8ae249c1f5a3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -0,0 +1,27 @@ +package dotty.tools.dotc +package coverage + +import scala.collection.mutable + +/** Holds a list of statements to include in the coverage reports. */ +class Coverage: + private val statementsById = new mutable.LongMap[Statement](256) + + def statements: Iterable[Statement] = statementsById.values + + def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt + +/** A statement that can be invoked, and thus counted as "covered" by code coverage tools. */ +case class Statement( + source: String, + location: Location, + id: Int, + start: Int, + end: Int, + line: Int, + desc: String, + symbolName: String, + treeName: String, + branch: Boolean, + ignored: Boolean = false +) \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala new file mode 100644 index 000000000000..faf1e97d0c01 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -0,0 +1,47 @@ +package dotty.tools.dotc +package coverage + +import ast.tpd._ +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags.* +import java.nio.file.Path + +/** Information about the location of a coverable piece of code. + * + * @param packageName name of the enclosing package + * @param className name of the closest enclosing class + * @param fullClassName fully qualified name of the closest enclosing class + * @param classType "type" of the closest enclosing class: Class, Trait or Object + * @param method name of the closest enclosing method + * @param sourcePath absolute path of the source file + */ +final case class Location( + packageName: String, + className: String, + fullClassName: String, + classType: String, + method: String, + sourcePath: Path +) + +object Location: + /** Extracts the location info of a Tree. */ + def apply(tree: Tree)(using ctx: Context): Location = + + val enclosingClass = ctx.owner.denot.enclosingClass + val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString + val className = enclosingClass.name.toSimpleName.toString + + val classType: String = + if enclosingClass.is(Trait) then "Trait" + else if enclosingClass.is(ModuleClass) then "Object" + else "Class" + + Location( + packageName, + className, + s"$packageName.$className", + classType, + ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(), + ctx.source.file.absolute.jpath + ) diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala new file mode 100644 index 000000000000..4bd0d89a0b84 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -0,0 +1,84 @@ +package dotty.tools.dotc +package coverage + +import java.nio.file.{Path, Paths, Files} +import java.io.Writer +import scala.language.unsafeNulls + +/** + * Serializes scoverage data. + * @see https://github.com/scoverage/scalac-scoverage-plugin/blob/main/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala + */ +object Serializer: + + private val CoverageFileName = "scoverage.coverage" + private val CoverageDataFormatVersion = "3.0" + + /** Write out coverage data to the given data directory, using the default coverage filename */ + def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit = + serialize(coverage, Paths.get(dataDir, CoverageFileName).toAbsolutePath, Paths.get(sourceRoot).toAbsolutePath) + + /** Write out coverage data to a file. */ + def serialize(coverage: Coverage, file: Path, sourceRoot: Path): Unit = + val writer = Files.newBufferedWriter(file) + try + serialize(coverage, writer, sourceRoot) + finally + writer.close() + + /** Write out coverage data (info about each statement that can be covered) to a writer. + */ + def serialize(coverage: Coverage, writer: Writer, sourceRoot: Path): Unit = + + def getRelativePath(filePath: Path): String = + val relPath = sourceRoot.relativize(filePath) + relPath.toString + + def writeHeader(writer: Writer): Unit = + writer.write(s"""# Coverage data, format version: $CoverageDataFormatVersion + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |""".stripMargin) + + def writeStatement(stmt: Statement, writer: Writer): Unit = + // Note: we write 0 for the count because we have not measured the actual coverage at this point + writer.write(s"""${stmt.id} + |${getRelativePath(stmt.location.sourcePath)} + |${stmt.location.packageName} + |${stmt.location.className} + |${stmt.location.classType} + |${stmt.location.fullClassName} + |${stmt.location.method} + |${stmt.start} + |${stmt.end} + |${stmt.line} + |${stmt.symbolName} + |${stmt.treeName} + |${stmt.branch} + |0 + |${stmt.ignored} + |${stmt.desc} + |\f + |""".stripMargin) + + writeHeader(writer) + coverage.statements.toSeq + .sortBy(_.id) + .foreach(stmt => writeStatement(stmt, writer)) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index ee943a0cb392..146040f4ce3e 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -161,7 +161,7 @@ object Completion { | prefix = ${completer.prefix}, | term = ${completer.mode.is(Mode.Term)}, | type = ${completer.mode.is(Mode.Type)} - | results = $backtickCompletions%, %""") + | results = $backtickedCompletions%, %""") (offset, backtickedCompletions) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 16926ecb72b8..643cf8c9caca 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -172,7 +172,7 @@ object Parsers { class Parser(source: SourceFile)(using Context) extends ParserCommon(source) { val in: Scanner = new Scanner(source) - //in.debugTokenStream = true // uncomment to see the token stream of the standard scanner, but not syntax highlighting + // in.debugTokenStream = true // uncomment to see the token stream of the standard scanner, but not syntax highlighting /** This is the general parse entry point. * Overridden by ScriptParser @@ -606,7 +606,7 @@ object Parsers { if startIndentWidth <= nextIndentWidth then i"""Line is indented too far to the right, or a `{` is missing before: | - |$t""" + |${t.tryToShow}""" else in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth), in.next.offset @@ -2873,7 +2873,7 @@ object Parsers { val isAccessMod = accessModifierTokens contains in.token val mods1 = addModifier(mods) loop(if (isAccessMod) accessQualifierOpt(mods1) else mods1) - else if (in.token == NEWLINE && (mods.hasFlags || mods.hasAnnotations)) { + else if (in.isNewLine && (mods.hasFlags || mods.hasAnnotations)) { in.nextToken() loop(mods) } @@ -3150,6 +3150,15 @@ object Parsers { syntaxError(i"source version import is only allowed at the toplevel", id.span) else if ctx.compilationUnit.sourceVersion.isDefined then syntaxError(i"duplicate source version import", id.span) + else if illegalSourceVersionNames.contains(imported) then + val candidate = + val nonMigration = imported.toString.replace("-migration", "") + validSourceVersionNames.find(_.show == nonMigration) + val baseMsg = i"`$imported` is not a valid source version" + val msg = candidate match + case Some(member) => i"$baseMsg, did you mean language.`$member`?" + case _ => baseMsg + syntaxError(msg, id.span) else ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString)) case None => diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index ca9346112862..0718cc4b8748 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -469,12 +469,6 @@ object Scanners { true } - def isContinuing(lastToken: Token) = - (openParensTokens.contains(token) || lastToken == RETURN) - && !pastBlankLine - && !migrateTo3 - && !noindentSyntax - /** The indentation width of the given offset. */ def indentWidth(offset: Offset): IndentWidth = import IndentWidth.{Run, Conc} @@ -565,6 +559,25 @@ object Scanners { handler(r) case _ => + /** Is this line seen as a continuation of last line? We assume that + * - last line ended in a token that can end a statement + * - current line starts with a token that can start a statement + * - current line does not start with a leading infix operator + * The answer is different for Scala-2 and Scala-3. + * - In Scala 2: Only `{` is treated as continuing, irrespective of indentation. + * But this is in fact handled by Parser.argumentStart which skips a NEWLINE, + * so we always assume false here. + * - In Scala 3: Only indented statements are treated as continuing, as long as + * they start with `(`, `[` or `{`, or the last statement ends in a `return`. + * The Scala 2 rules apply under source `3.0-migration` or under `-no-indent`. + */ + def isContinuing = + lastWidth < nextWidth + && (openParensTokens.contains(token) || lastToken == RETURN) + && !pastBlankLine + && !migrateTo3 + && !noindentSyntax + currentRegion match case r: Indented => indentIsSignificant = indentSyntax @@ -582,7 +595,7 @@ object Scanners { && canEndStatTokens.contains(lastToken) && canStartStatTokens.contains(token) && !isLeadingInfixOperator(nextWidth) - && !(lastWidth < nextWidth && isContinuing(lastToken)) + && !isContinuing then insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset) else if indentIsSignificant then diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 50e18d5587b9..e3f81adeee8a 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -1,47 +1,99 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package printing import scala.language.unsafeNulls +import scala.collection.mutable + import core._ import Texts._, Types._, Flags._, Symbols._, Contexts._ -import collection.mutable import Decorators._ -import scala.util.control.NonFatal import reporting.Message import util.DiffUtil import Highlighting._ object Formatting { + object ShownDef: + /** Represents a value that has been "shown" and can be consumed by StringFormatter. + * Not just a string because it may be a Seq that StringFormatter will intersperse with the trailing separator. + * Also, it's not a `String | Seq[String]` because then we'd need a Context to call `Showable#show`. We could + * make Context a requirement for a Show instance but then we'd have lots of instances instead of just one ShowAny + * instance. We could also try to make `Show#show` require the Context, but then that breaks the Conversion. */ + opaque type Shown = Any + object Shown: + given [A: Show]: Conversion[A, Shown] = Show[A].show(_) + + sealed abstract class Show[-T]: + /** Show a value T by returning a "shown" result. */ + def show(x: T): Shown + + /** The base implementation, passing the argument to StringFormatter which will try to `.show` it. */ + object ShowAny extends Show[Any]: + def show(x: Any): Shown = x + + class ShowImplicits2: + given Show[Product] = ShowAny + + class ShowImplicits1 extends ShowImplicits2: + given Show[ImplicitRef] = ShowAny + given Show[Names.Designator] = ShowAny + given Show[util.SrcPos] = ShowAny + + object Show extends ShowImplicits1: + inline def apply[A](using inline z: Show[A]): Show[A] = z + + given [X: Show]: Show[Seq[X]] with + def show(x: Seq[X]) = x.map(Show[X].show) + + given [A: Show, B: Show]: Show[(A, B)] with + def show(x: (A, B)) = (Show[A].show(x._1), Show[B].show(x._2)) + + given [X: Show]: Show[X | Null] with + def show(x: X | Null) = if x == null then "null" else Show[X].show(x.nn) + + given Show[FlagSet] with + def show(x: FlagSet) = x.flagsString + + given Show[TypeComparer.ApproxState] with + def show(x: TypeComparer.ApproxState) = TypeComparer.ApproxState.Repr.show(x) + + given Show[Showable] = ShowAny + given Show[Shown] = ShowAny + given Show[Int] = ShowAny + given Show[Char] = ShowAny + given Show[Boolean] = ShowAny + given Show[String] = ShowAny + given Show[Class[?]] = ShowAny + given Show[Throwable] = ShowAny + given Show[StringBuffer] = ShowAny + given Show[CompilationUnit] = ShowAny + given Show[Phases.Phase] = ShowAny + given Show[TyperState] = ShowAny + given Show[config.ScalaVersion] = ShowAny + given Show[io.AbstractFile] = ShowAny + given Show[parsing.Scanners.Scanner] = ShowAny + given Show[util.SourceFile] = ShowAny + given Show[util.Spans.Span] = ShowAny + given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny + end Show + end ShownDef + export ShownDef.{ Show, Shown } + /** General purpose string formatter, with the following features: * - * 1) On all Showables, `show` is called instead of `toString` - * 2) Exceptions raised by a `show` are handled by falling back to `toString`. - * 3) Sequences can be formatted using the desired separator between two `%` signs, + * 1. Invokes the `show` extension method on the interpolated arguments. + * 2. Sequences can be formatted using the desired separator between two `%` signs, * eg `i"myList = (${myList}%, %)"` - * 4) Safe handling of multi-line margins. Left margins are skipped om the parts + * 3. Safe handling of multi-line margins. Left margins are stripped on the parts * of the string context *before* inserting the arguments. That way, we guard * against accidentally treating an interpolated value as a margin. */ class StringFormatter(protected val sc: StringContext) { - protected def showArg(arg: Any)(using Context): String = arg match { - case arg: Showable => - try arg.show - catch { - case ex: CyclicReference => "... (caught cyclic reference) ..." - case NonFatal(ex) - if !ctx.mode.is(Mode.PrintShowExceptions) && - !ctx.settings.YshowPrintErrors.value => - val msg = ex match - case te: TypeError => te.toMessage - case _ => ex.getMessage - s"[cannot display due to $msg, raw string = ${arg.toString}]" - } - case _ => String.valueOf(arg) - } + protected def showArg(arg: Any)(using Context): String = arg.tryToShow - private def treatArg(arg: Any, suffix: String)(using Context): (Any, String) = arg match { + private def treatArg(arg: Shown, suffix: String)(using Context): (Any, String) = arg match { case arg: Seq[?] if suffix.nonEmpty && suffix.head == '%' => val (rawsep, rest) = suffix.tail.span(_ != '%') val sep = StringContext.processEscapes(rawsep) @@ -51,7 +103,7 @@ object Formatting { (showArg(arg), suffix) } - def assemble(args: Seq[Any])(using Context): String = { + def assemble(args: Seq[Shown])(using Context): String = { def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak def stripTrailingPart(s: String) = { val (pre, post) = s.span(c => !isLineBreak(c)) @@ -77,18 +129,6 @@ object Formatting { override protected def showArg(arg: Any)(using Context): String = wrapNonSensical(arg, super.showArg(arg)(using errorMessageCtx)) - class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) { - override protected def showArg(arg: Any)(using Context): String = - arg match { - case hl: Highlight => - hl.show - case hb: HighlightBuffer => - hb.toString - case _ => - SyntaxHighlighting.highlight(super.showArg(arg)) - } - } - private def wrapNonSensical(arg: Any, str: String)(using Context): String = { import Message._ def isSensical(arg: Any): Boolean = arg match { diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6d90b168a29f..9401efc58135 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -697,10 +697,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case MacroTree(call) => keywordStr("macro ") ~ toTextGlobal(call) - case Hole(isTermHole, idx, args) => - val (prefix, postfix) = if isTermHole then ("{{{ ", " }}}") else ("[[[ ", " ]]]") + case Hole(isTermHole, idx, args, content, tpt) => + val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]") val argsText = toTextGlobal(args, ", ") - prefix ~~ idx.toString ~~ "|" ~~ argsText ~~ postfix + val contentText = toTextGlobal(content) + val tptText = toTextGlobal(tpt) + prefix ~~ idx.toString ~~ "|" ~~ tptText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix case _ => tree.fallbackToText(this) } diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 9de869c994e6..2e0454c3a7aa 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -47,34 +47,67 @@ object PickledQuotes { changeOwnerOfTree(tpe1.typeTree, ctx.owner) } + /** `typeHole`/`types` argument of `QuoteUnpickler.{unpickleExpr,unpickleExprV2,unpickleType,unpickleTypeV2}` */ + enum TypeHole: + /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. + * From code compiled with Scala 3.0.x and 3.1.x. + * Note: For `unpickleType` it will always be `null`. + */ + case V1(evalHole: Null | ((Int, Seq[scala.quoted.Type[?]]) => scala.quoted.Type[?])) + /** `termHole` argument of `QuoteUnpickler.unpickleExprV2` + * From code compiled with Scala 3.2.0+ + */ + case V2(types: Null | Seq[scala.quoted.Type[?]]) + + def isEmpty: Boolean = this match + case V1(evalHole) => evalHole == null + case V2(types) => types == null + + enum ExprHole: + /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. + * From code compiled with Scala 3.0.x and 3.1.x. + * Note: For `unpickleType` it will always be `null`. + */ + case V1(evalHole: Null | ((Int, Seq[ExprHole.ArgV1], scala.quoted.Quotes) => scala.quoted.Expr[?])) + /** `termHole` argument of `QuoteUnpickler.unpickleExprV2` + * From code compiled with Scala 3.2.0+ + */ + case V2(evalHole: Null | ((Int, Seq[ExprHole.ArgV2], scala.quoted.Quotes) => scala.quoted.Expr[?])) + + object ExprHole: + type ArgV1 = scala.quoted.Type[?] | (Quotes ?=> scala.quoted.Expr[Any]) + type ArgV2 = scala.quoted.Type[?] | scala.quoted.Expr[Any] + /** Unpickle the tree contained in the TastyExpr */ - def unpickleTerm(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { + def unpickleTerm(pickled: String | List[String], typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false)) - val Inlined(call, Nil, expnasion) = unpickled + val Inlined(call, Nil, expansion) = unpickled val inlineCtx = inlineContext(call) - val expansion1 = spliceTypes(expnasion, typeHole, termHole)(using inlineCtx) + val expansion1 = spliceTypes(expansion, typeHole)(using inlineCtx) val expansion2 = spliceTerms(expansion1, typeHole, termHole)(using inlineCtx) cpy.Inlined(unpickled)(call, Nil, expansion2) } + /** Unpickle the tree contained in the TastyType */ - def unpickleTypeTree(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { + def unpickleTypeTree(pickled: String | List[String], typeHole: TypeHole)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = true)) - spliceTypes(unpickled, typeHole, termHole) + spliceTypes(unpickled, typeHole) } /** Replace all term holes with the spliced terms */ - private def spliceTerms(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { - val evaluateHoles = new TreeMap { + private def spliceTerms(tree: Tree, typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { + def evaluateHoles = new TreeMap { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { - case Hole(isTerm, idx, args) => + case Hole(isTermHole, idx, args, _, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { - val reifiedArgs = args.map { arg => - if (arg.isTerm) (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent) - else new TypeImpl(arg, SpliceScope.getCurrent) - } - if isTerm then - val quotedExpr = termHole(idx, reifiedArgs, QuotesImpl()) + if isTermHole then + val quotedExpr = termHole match + case ExprHole.V1(evalHole) => + evalHole.nn.apply(idx, reifyExprHoleV1Args(args), QuotesImpl()) + case ExprHole.V2(evalHole) => + evalHole.nn.apply(idx, reifyExprHoleV2Args(args), QuotesImpl()) + val filled = PickledQuotes.quotedExprToTree(quotedExpr) // We need to make sure a hole is created with the source file of the surrounding context, even if @@ -82,9 +115,13 @@ object PickledQuotes { if filled.source == ctx.source then filled else filled.cloneIn(ctx.source).withSpan(tree.span) else + // For backwards compatibility with 3.0.x and 3.1.x + // In 3.2.0+ all these holes are handled by `spliceTypes` before we call `spliceTerms`. + // // Replaces type holes generated by PickleQuotes (non-spliced types). // These are types defined in a quote and used at the same level in a nested quote. - val quotedType = typeHole(idx, reifiedArgs) + val TypeHole.V1(evalHole) = typeHole + val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) PickledQuotes.quotedTypeToTree(quotedType) } case tree => @@ -109,24 +146,34 @@ object PickledQuotes { } } } - val tree1 = evaluateHoles.transform(tree) + val tree1 = termHole match + case ExprHole.V2(null) => tree + case _ => evaluateHoles.transform(tree) quotePickling.println(i"**** evaluated quote\n$tree1") tree1 } /** Replace all type holes generated with the spliced types */ - private def spliceTypes(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Int], scala.quoted.Quotes) => Any)(using Context): Tree = { - tree match + private def spliceTypes(tree: Tree, typeHole: TypeHole)(using Context): Tree = { + if typeHole.isEmpty then tree + else tree match case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => val typeSpliceMap = (stat :: rest).iterator.map { case tdef: TypeDef => assert(tdef.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot)) - val tree = tdef.rhs match - case TypeBoundsTree(_, Hole(_, idx, args), _) => - val quotedType = typeHole(idx, args) - PickledQuotes.quotedTypeToTree(quotedType) - case TypeBoundsTree(_, tpt, _) => - tpt + val tree = typeHole match + case TypeHole.V1(evalHole) => + tdef.rhs match + case TypeBoundsTree(_, Hole(_, idx, args, _, _), _) => + // To keep for backwards compatibility. In some older version holes where created in the bounds. + val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) + PickledQuotes.quotedTypeToTree(quotedType) + case TypeBoundsTree(_, tpt, _) => + // To keep for backwards compatibility. In some older version we missed the creation of some holes. + tpt + case TypeHole.V2(types) => + val Hole(_, idx, _, _, _) = tdef.rhs + PickledQuotes.quotedTypeToTree(types.nn.apply(idx)) (tdef.symbol, tree.tpe) }.toMap class ReplaceSplicedTyped extends TypeMap() { @@ -148,6 +195,21 @@ object PickledQuotes { tree } + def reifyTypeHoleArgs(args: List[Tree])(using Context): List[scala.quoted.Type[?]] = + args.map(arg => new TypeImpl(arg, SpliceScope.getCurrent)) + + def reifyExprHoleV1Args(args: List[Tree])(using Context): List[ExprHole.ArgV1] = + args.map { arg => + if arg.isTerm then (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent) + else new TypeImpl(arg, SpliceScope.getCurrent) + } + + def reifyExprHoleV2Args(args: List[Tree])(using Context): List[ExprHole.ArgV2] = + args.map { arg => + if arg.isTerm then new ExprImpl(arg, SpliceScope.getCurrent) + else new TypeImpl(arg, SpliceScope.getCurrent) + } + // TASTY picklingtests/pos/quoteTest.scala /** Pickle tree into it's TASTY bytes s*/ diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f8fec07e9f9f..797073115bb5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1510,13 +1510,6 @@ import transform.SymUtils._ def explain = "" } - class TopLevelCantBeImplicit(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(TopLevelCantBeImplicitID) { - def msg = em"""${hl("implicit")} modifier cannot be used for top-level definitions""" - def explain = "" - } - class TypesAndTraitsCantBeImplicit()(using Context) extends SyntaxMsg(TypesAndTraitsCantBeImplicitID) { def msg = em"""${hl("implicit")} modifier cannot be used for types or traits""" @@ -1759,7 +1752,9 @@ import transform.SymUtils._ class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context) extends NamingMsg(ClassAndCompanionNameClashID) { - def msg = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix}" + def msg = + val name = cls.name.stripModuleClassSuffix + em"Name clash: both ${cls.owner} and its companion object defines $name" def explain = em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name: | - ${cls.owner} defines ${cls} @@ -2126,6 +2121,7 @@ import transform.SymUtils._ class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) extends NamingMsg(DoubleDefinitionID) { def msg = { def nameAnd = if (decl.name != previousDecl.name) " name and" else "" + def erasedType = if ctx.erasedTypes then i" ${decl.info}" else "" def details(using Context): String = if (decl.isRealMethod && previousDecl.isRealMethod) { import Signature.MatchDegree._ @@ -2153,7 +2149,7 @@ import transform.SymUtils._ |Consider adding a @targetName annotation to one of the conflicting definitions |for disambiguation.""" else "" - i"have the same$nameAnd type after erasure.$hint" + i"have the same$nameAnd type$erasedType after erasure.$hint" } } else "" @@ -2172,10 +2168,12 @@ import transform.SymUtils._ else "Name clash between inherited members" - em"""$clashDescription: - |${previousDecl.showDcl} ${symLocation(previousDecl)} and - |${decl.showDcl} ${symLocation(decl)} - |""" + details + atPhase(typerPhase) { + em"""$clashDescription: + |${previousDecl.showDcl} ${symLocation(previousDecl)} and + |${decl.showDcl} ${symLocation(decl)} + |""" + } + details } def explain = "" } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 1120b279b2e2..c22172b3e10f 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -776,7 +776,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case n: Name => h = nameHash(n, h) case elem => - cannotHash(what = i"`$elem` of unknown class ${elem.getClass}", elem, tree) + cannotHash(what = i"`${elem.tryToShow}` of unknown class ${elem.getClass}", elem, tree) h end iteratorHash diff --git a/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala b/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala index 99a4da63128a..8aed9e5b9771 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala @@ -143,8 +143,7 @@ class SemanticdbInputStream private (buffer: Array[Byte], input: InputStream) { throw new IllegalStateException( s"refillBuffer() called when $n bytes were already available in buffer") } - if (totalBytesRetired + bufferPos + n > currentLimit) false - else if (input != null) { + if totalBytesRetired + bufferPos + n <= currentLimit && input != null then val pos: Int = bufferPos if (pos > 0) { if (bufferSize > pos) { @@ -166,7 +165,6 @@ class SemanticdbInputStream private (buffer: Array[Byte], input: InputStream) { recomputeBufferSizeAfterLimit() return ((bufferSize >= n) || tryRefillBuffer(n)) } - } false } diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 15c6d30c3b19..a59ce4d79a48 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -58,7 +58,7 @@ abstract class AccessProxies { /** Add all needed accessors to the `body` of class `cls` */ def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = { - val accDefs = accessorDefs(cls) + val accDefs = accessorDefs(cls).toList transforms.println(i"add accessors for $cls: $accDefs%, %") if (accDefs.isEmpty) body else body ++ accDefs } diff --git a/compiler/src/dotty/tools/dotc/transform/Bridges.scala b/compiler/src/dotty/tools/dotc/transform/Bridges.scala index 912b8ca69131..071109147b45 100644 --- a/compiler/src/dotty/tools/dotc/transform/Bridges.scala +++ b/compiler/src/dotty/tools/dotc/transform/Bridges.scala @@ -170,7 +170,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) { * time deferred methods in `stats` that are replaced by a bridge with the same signature. */ def add(stats: List[untpd.Tree]): List[untpd.Tree] = - val opc = new BridgesCursor()(using preErasureCtx) + val opc = inContext(preErasureCtx) { new BridgesCursor } while opc.hasNext do if !opc.overriding.is(Deferred) then addBridgeIfNeeded(opc.overriding, opc.overridden) diff --git a/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala b/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala index ca6a056fd6d5..a363ccaeb0d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala @@ -80,7 +80,7 @@ class DropOuterAccessors extends MiniPhase with IdentityDenotTransformer: cpy.Block(rhs)(inits.filterNot(dropOuterInit), expr) }) assert(droppedParamAccessors.isEmpty, - i"""Failed to eliminate: $droppedParamAccessors + i"""Failed to eliminate: ${droppedParamAccessors.toList} when dropping outer accessors for ${ctx.owner} with $impl""") cpy.Template(impl)(constr = constr1, body = body1) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index ab0c8d449d00..123f2a7aaeb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -266,7 +266,7 @@ object Erasure { def constant(tree: Tree, const: Tree)(using Context): Tree = (if (isPureExpr(tree)) const else Block(tree :: Nil, const)).withSpan(tree.span) - final def box(tree: Tree, target: => String = "")(using Context): Tree = trace(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { + final def box(tree: Tree, target: => String = "")(using Context): Tree = trace(i"boxing ${tree.showSummary()}: ${tree.tpe} into $target") { tree.tpe.widen match { case ErasedValueType(tycon, _) => New(tycon, cast(tree, underlyingOfValueClass(tycon.symbol.asClass)) :: Nil) // todo: use adaptToType? @@ -286,7 +286,7 @@ object Erasure { } } - def unbox(tree: Tree, pt: Type)(using Context): Tree = trace(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") { + def unbox(tree: Tree, pt: Type)(using Context): Tree = trace(i"unboxing ${tree.showSummary()}: ${tree.tpe} as a $pt") { pt match { case ErasedValueType(tycon, underlying) => def unboxedTree(t: Tree) = @@ -1031,7 +1031,7 @@ object Erasure { } override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = - trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { + trace(i"adapting ${tree.showSummary()}: ${tree.tpe} to $pt", show = true) { if ctx.phase != erasurePhase && ctx.phase != erasurePhase.next then // this can happen when reading annotations loaded during erasure, // since these are loaded at phase typer. diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala index a7f8421f51d0..0a9798226d76 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -278,7 +278,8 @@ object ExplicitOuter { ) case _ => false } - def hasOuterPrefix(tp: Type) = tp match { + def hasOuterPrefix(tp: Type): Boolean = tp.stripped match { + case AppliedType(tycon, _) => hasOuterPrefix(tycon) case TypeRef(prefix, _) => isOuterRef(prefix) case _ => false } diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index 3233601310ae..edbfbd1552c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -60,11 +60,12 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase /** If argument is complex, hoist it out into its own method and refer to the * method instead. - * @param arg The argument that might be hoisted - * @param cdef The definition of the constructor from which the call is made + * @param arg The argument that might be hoisted + * @param cdef The definition of the constructor from which the call is made + * @param lifted Argument definitions that were lifted out in a call prefix * @return The argument after possible hoisting */ - private def hoistSuperArg(arg: Tree, cdef: DefDef): Tree = { + private def hoistSuperArg(arg: Tree, cdef: DefDef, lifted: List[Symbol]): Tree = { val constr = cdef.symbol lazy val origParams = // The parameters that can be accessed in the supercall if (constr == cls.primaryConstructor) @@ -92,36 +93,39 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase val argTypeWrtConstr = argType.widenTermRefExpr.subst(origParams, allParamRefs(constr.info)) // argType with references to paramRefs of the primary constructor instead of // local parameter accessors + val abstractedArgType = + if lifted.isEmpty then argTypeWrtConstr + else MethodType.fromSymbols(lifted, argTypeWrtConstr) newSymbol( owner = methOwner, name = SuperArgName.fresh(cls.name.toTermName), flags = Synthetic | Private | Method | staticFlag, - info = replaceResult(constr.info, argTypeWrtConstr), + info = replaceResult(constr.info, abstractedArgType), coord = constr.coord ).enteredAfter(thisPhase) } /** Type of a reference implies that it needs to be hoisted */ def refNeedsHoist(tp: Type): Boolean = tp match { - case tp: ThisType => !tp.cls.isStaticOwner && tp.cls != cls + case tp: ThisType => !tp.cls.isStaticOwner && !cls.isContainedIn(tp.cls) case tp: TermRef => refNeedsHoist(tp.prefix) case _ => false } /** Super call argument is complex, needs to be hoisted */ - def needsHoist(tree: Tree) = tree match { + def needsHoist(tree: Tree) = tree match case _: DefDef => true case _: Template => true case _: New => !tree.tpe.typeSymbol.isStatic case _: RefTree | _: This => refNeedsHoist(tree.tpe) case _ => false - } /** Only rewire types that are owned by the current Hoister and is an param or accessor */ def needsRewire(tp: Type) = tp match { case ntp: NamedType => val owner = ntp.symbol.maybeOwner (owner == cls || owner == constr) && ntp.symbol.isParamOrAccessor + || lifted.contains(ntp.symbol) case _ => false } @@ -134,7 +138,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase if pref.isType then pref.tpe.typeSymbol else pref.symbol) val tmap = new TreeTypeMap( typeMap = new TypeMap { - lazy val origToParam = origParams.zip(paramSyms).toMap + lazy val origToParam = (origParams ::: lifted).zip(paramSyms).toMap def apply(tp: Type) = tp match { case tp: NamedType if needsRewire(tp) => origToParam.get(tp.symbol) match { @@ -164,9 +168,11 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase Nil } val (typeParams, termParams) = origParams.span(_.isType) - val res = ref(superMeth) + var res = ref(superMeth) .appliedToTypes(typeParams.map(_.typeRef)) .appliedToArgss(termParamRefs(constr.info, termParams)) + if lifted.nonEmpty then + res = res.appliedToArgs(lifted.map(ref)) report.log(i"hoist $arg, cls = $cls = $res") res case _ => arg @@ -174,25 +180,40 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase } /** Hoist complex arguments in super call out of the class. */ - def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef): Tree = superCall match { + def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef, lifted: mutable.ListBuffer[Symbol]): Tree = superCall match + case Block(defs, expr) => + cpy.Block(superCall)( + stats = defs.mapconserve { + case vdef: ValDef => + try cpy.ValDef(vdef)(rhs = hoistSuperArg(vdef.rhs, cdef, lifted.toList)) + finally lifted += vdef.symbol + case ddef: DefDef => + try cpy.DefDef(ddef)(rhs = hoistSuperArg(ddef.rhs, cdef, lifted.toList)) + finally lifted += ddef.symbol + case stat => + stat + }, + expr = hoistSuperArgsFromCall(expr, cdef, lifted)) case Apply(fn, args) => - cpy.Apply(superCall)(hoistSuperArgsFromCall(fn, cdef), args.mapconserve(hoistSuperArg(_, cdef))) + cpy.Apply(superCall)( + hoistSuperArgsFromCall(fn, cdef, lifted), + args.mapconserve(hoistSuperArg(_, cdef, lifted.toList))) case _ => superCall - } /** Hoist complex arguments in this-constructor call of secondary constructor out of the class. */ def hoistSuperArgsFromConstr(stat: Tree): Tree = stat match { - case stat: DefDef if stat.symbol.isClassConstructor => - cpy.DefDef(stat)(rhs = - stat.rhs match { - case Block(superCall :: stats, expr) => - val superCall1 = hoistSuperArgsFromCall(superCall, stat) - if (superCall1 eq superCall) stat.rhs - else cpy.Block(stat.rhs)(superCall1 :: stats, expr) + case constr: DefDef if constr.symbol.isClassConstructor => + val lifted = new mutable.ListBuffer[Symbol] + cpy.DefDef(constr)(rhs = + constr.rhs match + case Block(stats @ (superCall :: stats1), expr: Literal) => + cpy.Block(constr.rhs)( + stats.derivedCons(hoistSuperArgsFromCall(superCall, constr, lifted), stats1), + expr) case _ => - hoistSuperArgsFromCall(stat.rhs, stat) - }) + hoistSuperArgsFromCall(constr.rhs, constr, lifted) + ) case _ => stat } @@ -202,7 +223,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase tdef.rhs match { case impl @ Template(cdef, superCall :: others, _, _) => val hoist = new Hoister(tdef.symbol) - val hoistedSuperCall = hoist.hoistSuperArgsFromCall(superCall, cdef) + val hoistedSuperCall = hoist.hoistSuperArgsFromCall(superCall, cdef, new mutable.ListBuffer) val hoistedBody = impl.body.mapconserve(hoist.hoistSuperArgsFromConstr) if (hoist.superArgDefs.isEmpty) tdef else { diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index ca17cf607641..3c5da3ffadf3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -69,8 +69,6 @@ class Inlining extends MacroTransform { if tree1.tpe.isError then tree1 else Inliner.inlineCall(tree1) case _: GenericApply if tree.symbol.isQuote => - if level == 0 then - ctx.compilationUnit.needsQuotePickling = true super.transform(tree)(using StagingContext.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => super.transform(tree)(using StagingContext.spliceContext) diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala new file mode 100644 index 000000000000..442bf27d9f6b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -0,0 +1,333 @@ +package dotty.tools.dotc +package transform + +import java.io.File +import java.util.concurrent.atomic.AtomicInteger + +import collection.mutable +import core.Flags.* +import core.Contexts.{Context, ctx, inContext} +import core.DenotTransformers.IdentityDenotTransformer +import core.Symbols.{defn, Symbol} +import core.Decorators.{toTermName, i} +import core.Constants.Constant +import core.NameOps.isContextFunction +import core.Types.* +import typer.LiftCoverage +import util.{SourcePosition, Property} +import util.Spans.Span +import coverage.* + +/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker + * ("instruments" the source code). + * The result can then be consumed by the Scoverage tool. + */ +class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: + import ast.tpd._ + + override def phaseName = InstrumentCoverage.name + + override def description = InstrumentCoverage.description + + // Enabled by argument "-coverage-out OUTPUT_DIR" + override def isEnabled(using ctx: Context) = + ctx.settings.coverageOutputDir.value.nonEmpty + + // counter to assign a unique id to each statement + private var statementId = 0 + + // stores all instrumented statements + private val coverage = Coverage() + + override def run(using ctx: Context): Unit = + val outputPath = ctx.settings.coverageOutputDir.value + + // Ensure the dir exists + val dataDir = new File(outputPath) + val newlyCreated = dataDir.mkdirs() + + if !newlyCreated then + // If the directory existed before, let's clean it up. + dataDir.listFiles.nn + .filter(_.nn.getName.nn.startsWith("scoverage")) + .foreach(_.nn.delete()) + end if + super.run + + Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value) + + override protected def newTransformer(using Context) = CoverageTransformer() + + /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */ + private class CoverageTransformer extends Transformer: + override def transform(tree: Tree)(using ctx: Context): Tree = + inContext(transformCtx(tree)) { // necessary to position inlined code properly + tree match + // simple cases + case tree: (Import | Export | Literal | This | Super | New) => tree + case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident, TypTree, ... + + // branches + case tree: If => + cpy.If(tree)( + cond = transform(tree.cond), + thenp = instrument(transform(tree.thenp), branch = true), + elsep = instrument(transform(tree.elsep), branch = true) + ) + case tree: Try => + cpy.Try(tree)( + expr = instrument(transform(tree.expr), branch = true), + cases = instrumentCases(tree.cases), + finalizer = instrument(transform(tree.finalizer), branch = true) + ) + + // a.f(args) + case tree @ Apply(fun: Select, args) => + // don't transform the first Select, but do transform `a.b` in `a.b.f(args)` + val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) + if canInstrumentApply(tree) then + if needsLift(tree) then + val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted + instrumentLifted(transformed) + else + val transformed = transformApply(tree, transformedFun) + instrument(transformed) + else + transformApply(tree, transformedFun) + + // f(args) + case tree: Apply => + if canInstrumentApply(tree) then + if needsLift(tree) then + instrumentLifted(tree) + else + instrument(transformApply(tree)) + else + transformApply(tree) + + // (f(x))[args] + case TypeApply(fun: Apply, args) => + cpy.TypeApply(tree)(transform(fun), args) + + // a.b + case Select(qual, name) => + if qual.symbol.exists && qual.symbol.is(JavaDefined) then + //Java class can't be used as a value, we can't instrument the + //qualifier ({;System}.xyz() is not possible !) instrument it + //as it is + instrument(tree) + else + val transformed = cpy.Select(tree)(transform(qual), name) + if transformed.qualifier.isDef then + // instrument calls to methods without parameter list + instrument(transformed) + else + transformed + + case tree: CaseDef => instrumentCaseDef(tree) + case tree: ValDef => + // only transform the rhs + val rhs = transform(tree.rhs) + cpy.ValDef(tree)(rhs = rhs) + + case tree: DefDef => + // Only transform the params (for the default values) and the rhs. + val paramss = transformParamss(tree.paramss) + val rhs = transform(tree.rhs) + val finalRhs = + if canInstrumentDefDef(tree) then + // Ensure that the rhs is always instrumented, if possible + instrumentBody(tree, rhs) + else + rhs + cpy.DefDef(tree)(tree.name, paramss, tree.tpt, finalRhs) + + case tree: PackageDef => + // only transform the statements of the package + cpy.PackageDef(tree)(tree.pid, transform(tree.stats)) + case tree: Assign => + // only transform the rhs + cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) + + // For everything else just recurse and transform + // Special care for Templates: it's important to set the owner of the `stats`, like super.transform + case _ => + super.transform(tree) + } + + /** Lifts and instruments an application. + * Note that if only one arg needs to be lifted, we just lift everything. + */ + private def instrumentLifted(tree: Apply)(using Context) = + // lifting + val buffer = mutable.ListBuffer[Tree]() + val liftedApply = LiftCoverage.liftForCoverage(buffer, tree) + + // instrumentation + val instrumentedArgs = buffer.toList.map(transform) + val instrumentedApply = instrument(liftedApply) + Block( + instrumentedArgs, + instrumentedApply + ) + + private inline def transformApply(tree: Apply)(using Context): Apply = + transformApply(tree, transform(tree.fun)) + + private inline def transformApply(tree: Apply, transformedFun: Tree)(using Context): Apply = + cpy.Apply(tree)(transformedFun, transform(tree.args)) + + private inline def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] = + cases.map(instrumentCaseDef) + + private def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = + val pat = tree.pat + val guard = tree.guard + val friendlyEnd = if guard.span.exists then guard.span.end else pat.span.end + val pos = tree.sourcePos.withSpan(tree.span.withEnd(friendlyEnd)) // user-friendly span + // ensure that the body is always instrumented by inserting a call to Invoker.invoked at its beginning + val instrumentedBody = instrument(transform(tree.body), pos, false) + cpy.CaseDef(tree)(tree.pat, transform(tree.guard), instrumentedBody) + + /** Records information about a new coverable statement. Generates a unique id for it. + * @return the statement's id + */ + private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int = + val id = statementId + statementId += 1 + val statement = new Statement( + source = ctx.source.file.name, + location = Location(tree), + id = id, + start = pos.start, + end = pos.end, + line = pos.line, + desc = tree.source.content.slice(pos.start, pos.end).mkString, + symbolName = tree.symbol.name.toSimpleName.toString, + treeName = tree.getClass.getSimpleName.nn, + branch + ) + coverage.addStatement(statement) + id + + private inline def syntheticSpan(pos: SourcePosition): Span = pos.span.toSynthetic + + /** Shortcut for instrument(tree, tree.sourcePos, branch) */ + private inline def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = + instrument(tree, tree.sourcePos, branch) + + /** Instruments a statement, if it has a position. */ + private def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using Context): Tree = + if pos.exists && !pos.span.isZeroExtent then + val statementId = recordStatement(tree, pos, branch) + insertInvokeCall(tree, pos, statementId) + else + tree + + /** Instruments the body of a DefDef. Handles corner cases. */ + private def instrumentBody(parent: DefDef, body: Tree)(using Context): Tree = + /* recurse on closures, so that we insert the call at the leaf: + + def g: (a: Ta) ?=> (b: Tb) = { + // nothing here <-- not here! + def $anonfun(using a: Ta) = + Invoked.invoked(id, DIR) <-- here + + closure($anonfun) + } + */ + body match + case b @ Block((meth: DefDef) :: Nil, closure: Closure) + if meth.symbol == closure.meth.symbol && defn.isContextFunctionType(body.tpe) => + val instr = cpy.DefDef(meth)(rhs = instrumentBody(parent, meth.rhs)) + cpy.Block(b)(instr :: Nil, closure) + case _ => + // compute user-friendly position to highlight more text in the coverage UI + val namePos = parent.namePos + val pos = namePos.withSpan(namePos.span.withStart(parent.span.start)) + // record info and insert call to Invoker.invoked + val statementId = recordStatement(parent, pos, false) + insertInvokeCall(body, pos, statementId) + + /** Returns the tree, prepended by a call to Invoker.invoker */ + private def insertInvokeCall(tree: Tree, pos: SourcePosition, statementId: Int)(using Context): Tree = + val callSpan = syntheticSpan(pos) + Block(invokeCall(statementId, callSpan) :: Nil, tree).withSpan(callSpan.union(tree.span)) + + /** Generates Invoked.invoked(id, DIR) */ + private def invokeCall(id: Int, span: Span)(using Context): Tree = + val outputPath = ctx.settings.coverageOutputDir.value + ref(defn.InvokedMethodRef).withSpan(span) + .appliedToArgs( + List(Literal(Constant(id)), Literal(Constant(outputPath))) + ).withSpan(span) + + /** + * Checks if the apply needs a lift in the coverage phase. + * In case of a nested application, we have to lift all arguments + * Example: + * ``` + * def T(x:Int)(y:Int) + * T(f())(1) + * ``` + * should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)} + */ + private def needsLift(tree: Apply)(using Context): Boolean = + def isBooleanOperator(fun: Tree) = + // We don't want to lift a || getB(), to avoid calling getB if a is true. + // Same idea with a && getB(): if a is false, getB shouldn't be called. + val sym = fun.symbol + sym.exists && + sym == defn.Boolean_&& || sym == defn.Boolean_|| + + def isContextual(fun: Apply): Boolean = + val args = fun.args + args.nonEmpty && args.head.symbol.isAllOf(Given | Implicit) + + val fun = tree.fun + val nestedApplyNeedsLift = fun match + case a: Apply => needsLift(a) + case _ => false + + nestedApplyNeedsLift || + !isBooleanOperator(fun) && !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift) + + /** Check if the body of a DefDef can be instrumented with instrumentBody. */ + private def canInstrumentDefDef(tree: DefDef)(using Context): Boolean = + // No need to force the instrumentation of synthetic definitions + // (it would work, but it looks better without). + !tree.symbol.isOneOf(Accessor | Synthetic | Artifact) && + !tree.rhs.isEmpty + + /** Check if an Apply can be instrumented. Prevents this phase from generating incorrect code. */ + private def canInstrumentApply(tree: Apply)(using Context): Boolean = + !tree.symbol.isOneOf(Synthetic | Artifact) && // no need to instrument synthetic apply + (tree.typeOpt match + case AppliedType(tycon: NamedType, _) => + /* If the last expression in a block is a context function, we'll try to + summon its arguments at the current point, even if the expected type + is a function application. Therefore, this is not valid: + ``` + def f = (t: Exception) ?=> (c: String) ?=> result + + ({ + invoked() + f(using e) + })(using s) + ``` + */ + !tycon.name.isContextFunction + case m: MethodType => + /* def f(a: Ta)(b: Tb) + f(a)(b) + + Here, f(a)(b) cannot be rewritten to {invoked();f(a)}(b) + */ + false + case _ => + true + ) + +object InstrumentCoverage: + val name: String = "instrumentCoverage" + val description: String = "instrument code for coverage cheking" diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index b5881d88f5f1..4ab6a5c9a646 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -214,6 +214,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => initFlags = stat.symbol.flags | PrivateLocal ).installAfter(thisPhase) stat.symbol.enteredAfter(thisPhase) + case _ => } (scall, stats ::: inits, args) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 82a187f57109..a12ef6d50bed 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -13,6 +13,7 @@ import ast.TreeTypeMap import SymUtils._ import NameKinds._ import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.config.ScalaRelease.* import scala.collection.mutable import dotty.tools.dotc.core.Annotations._ @@ -23,45 +24,52 @@ import dotty.tools.dotc.typer.Inliner import scala.annotation.constructorOnly - -/** Translates quoted terms and types to `unpickleExpr` or `unpickleType` method calls. +/** Translates quoted terms and types to `unpickleExprV2` or `unpickleType` method calls. * * Transforms top level quote * ``` * '{ ... - * val x1 = ??? - * val x2 = ??? + * @TypeSplice type X0 = {{ 0 | .. | contentsTpe0 | .. }} + * @TypeSplice type X2 = {{ 1 | .. | contentsTpe1 | .. }} + * val x1: U1 = ??? + * val x2: U2 = ??? + * ... + * {{{ 3 | x1 | contents0 | T0 }}} // hole + * ... + * {{{ 4 | x2 | contents1 | T1 }}} // hole * ... - * ${ ... '{ ... x1 ... x2 ...} ... } + * {{{ 5 | x1, x2 | contents2 | T2 }}} // hole * ... * } * ``` * to * ``` - * unpickleExpr( + * unpickleExprV2( * pickled = [[ // PICKLED TASTY - * ... + * @TypeSplice type X0 // with bounds that do not contain captured types + * @TypeSplice type X1 // with bounds that do not contain captured types * val x1 = ??? * val x2 = ??? * ... - * Hole( | x1, x2) + * {{{ 0 | x1 | | T0 }}} // hole + * ... + * {{{ 1 | x2 | | T1 }}} // hole + * ... + * {{{ 2 | x1, x2 | | T2 }}} // hole * ... * ]], * typeHole = (idx: Int, args: List[Any]) => idx match { - * case 0 => ... + * case 0 => contentsTpe0.apply(args(0).asInstanceOf[Type[?]]) // beta reduced + * case 1 => contentsTpe1.apply(args(0).asInstanceOf[Type[?]]) // beta reduced * }, - * termHole = (idx: Int, args: List[Any], qctx: Quotes) => idx match { - * case 0 => ... - * ... - * case => - * val x1$1 = args(0).asInstanceOf[Expr[T]] - * val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]] - * ... - * { ... '{ ... ${x1$1} ... ${x2$1} ...} ... } + * termHole = (idx: Int, args: List[Any], quotes: Quotes) => idx match { + * case 3 => content0.apply(args(0).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced + * case 4 => content1.apply(args(0).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced + * case 5 => content2.apply(args(0).asInstanceOf[Expr[U1]], args(1).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced * }, * ) * ``` - * and then performs the same transformation on `'{ ... ${x1$1} ... ${x2$1} ...}`. + * and then performs the same transformation on any quote contained in the `content`s. * */ class PickleQuotes extends MacroTransform { @@ -75,375 +83,80 @@ class PickleQuotes extends MacroTransform { override def allowsImplicitSearch: Boolean = true override def checkPostCondition(tree: Tree)(using Context): Unit = - tree match { + tree match case tree: RefTree if !Inliner.inInlineMethod => assert(!tree.symbol.isQuote) assert(!tree.symbol.isExprSplice) - case _ : TypeDef => + case _ : TypeDef if !Inliner.inInlineMethod => assert(!tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot), s"${tree.symbol} should have been removed by PickledQuotes because it has a @quoteTypeTag") case _ => - } override def run(using Context): Unit = - if (ctx.compilationUnit.needsQuotePickling) super.run(using freshStagingContext) + if (ctx.compilationUnit.needsStaging) super.run(using freshStagingContext) protected def newTransformer(using Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = - new QuoteReifier(null, new mutable.HashMap[Symbol, Tree => Tree], new Embedded, ctx.owner)(ctx).transform(tree) + tree match + case Apply(Select(Apply(TypeApply(fn, List(tpt)), List(code)),nme.apply), List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + val (contents, codeWithHoles) = makeHoles(code) + val sourceRef = Inliner.inlineCallTrace(ctx.owner, tree.sourcePos) + val codeWithHoles2 = Inlined(sourceRef, Nil, codeWithHoles) + val pickled = PickleQuotes(quotes, codeWithHoles2, contents, tpt.tpe, false) + transform(pickled) // pickle quotes that are in the contents + case Apply(TypeApply(_, List(tpt)), List(quotes)) if tree.symbol == defn.QuotedTypeModule_of => + tpt match + case Select(t, _) if tpt.symbol == defn.QuotedType_splice => + // `Type.of[t.Underlying](quotes)` --> `t` + ref(t.symbol)(using ctx.withSource(tpt.source)).withSpan(tpt.span) + case _ => + val (contents, tptWithHoles) = makeHoles(tpt) + PickleQuotes(quotes, tptWithHoles, contents, tpt.tpe, true) + case tree: DefDef if tree.symbol.is(Macro) => + // Shrink size of the tree. The methods have already been inlined. + // TODO move to FirstTransform to trigger even without quotes + cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + case _: DefDef if tree.symbol.isInlineMethod => + tree + case _ => + super.transform(tree) } - /** The main transformer class - * @param outer the next outer reifier, null is this is the topmost transformer - * @param embedded a list of embedded quotes (if in a splice) or splices (if in a quote) - * @param owner the owner in the destination lifted lambda - * @param capturers register a reference defined in a quote but used in another quote nested in a splice. - * Returns a version of the reference that needs to be used in its place. - * '{ - * val x = ??? - * ${ ... '{ ... x ... } ... } - * } - * Eta expanding the `x` in `${ ... '{ ... x ... } ... }` will return a `${x$1}` for which the `x$1` - * be created by some outer reifier. - * This transformation is only applied to definitions at staging level 1. - * See `isCaptured`. - */ - private class QuoteReifier(outer: QuoteReifier | Null, capturers: mutable.HashMap[Symbol, Tree => Tree], - val embedded: Embedded, val owner: Symbol)(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { self => - - import StagingContext._ - - /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ - def nested(isQuote: Boolean)(using Context): QuoteReifier = { - val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded - new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx) - } - - /** Split `body` into a core and a list of embedded splices. - * Then if inside a splice, make a hole from these parts. - * If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or - * `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with - * core and splices as arguments. - */ - override protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree = { - val isType = quote.symbol eq defn.QuotedTypeModule_of - if (level > 0) { - val body1 = nested(isQuote = true).transform(body)(using quoteContext) - super.transformQuotation(body1, quote) - } - else { - val (body1, splices) = nested(isQuote = true).splitQuote(body)(using quoteContext) - if (level == 0) { - val body2 = - if (body1.isType) body1 - else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1) - pickledQuote(quote, body2, splices, body.tpe, isType).withSpan(quote.span) - } - else - body - } - } - - private def pickledQuote(quote: Apply, body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(using Context) = { - /** Encode quote using Reflection.Literal - * - * Generate the code - * ```scala - * qctx => qctx.reflect.TreeMethods.asExpr( - * qctx.reflect.Literal.apply(x$1.reflect.Constant..apply()) - * ).asInstanceOf[scala.quoted.Expr[]] - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def pickleAsLiteral(lit: Literal) = { - val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, exprType) - def mkConst(ts: List[Tree]) = { - val reflect = ts.head.select("reflect".toTermName) - val typeName = body.tpe.typeSymbol.name - val literalValue = - if lit.const.tag == Constants.NullTag || lit.const.tag == Constants.UnitTag then Nil - else List(body) - val constant = reflect.select(s"${typeName}Constant".toTermName).select(nme.apply).appliedToTermArgs(literalValue) - val literal = reflect.select("Literal".toTermName).select(nme.apply).appliedTo(constant) - reflect.select("TreeMethods".toTermName).select("asExpr".toTermName).appliedTo(literal).asInstance(exprType) - } - Lambda(lambdaTpe, mkConst).withSpan(body.span) - } - - /** Encode quote using Reflection.Literal - * - * Generate the code - * ```scala - * qctx => scala.quoted.ToExpr.{BooleanToExpr,ShortToExpr, ...}.apply()(qctx) - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def liftedValue(lit: Literal, lifter: Symbol) = - val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, exprType) - def mkToExprCall(ts: List[Tree]) = - ref(lifter).appliedToType(originalTp).select(nme.apply).appliedTo(lit).appliedTo(ts.head) - Lambda(lambdaTpe, mkToExprCall).withSpan(body.span) - - def pickleAsValue(lit: Literal) = { - // TODO should all constants be pickled as Literals? - // Should examime the generated bytecode size to decide and performance - lit.const.tag match { - case Constants.NullTag => pickleAsLiteral(lit) - case Constants.UnitTag => pickleAsLiteral(lit) - case Constants.BooleanTag => liftedValue(lit, defn.ToExprModule_BooleanToExpr) - case Constants.ByteTag => liftedValue(lit, defn.ToExprModule_ByteToExpr) - case Constants.ShortTag => liftedValue(lit, defn.ToExprModule_ShortToExpr) - case Constants.IntTag => liftedValue(lit, defn.ToExprModule_IntToExpr) - case Constants.LongTag => liftedValue(lit, defn.ToExprModule_LongToExpr) - case Constants.FloatTag => liftedValue(lit, defn.ToExprModule_FloatToExpr) - case Constants.DoubleTag => liftedValue(lit, defn.ToExprModule_DoubleToExpr) - case Constants.CharTag => liftedValue(lit, defn.ToExprModule_CharToExpr) - case Constants.StringTag => liftedValue(lit, defn.ToExprModule_StringToExpr) - } - } - - /** Encode quote using QuoteUnpickler.{unpickleExpr, unpickleType} - * - * Generate the code - * ```scala - * qctx => qctx.asInstanceOf[QuoteUnpickler].[]( - * , - * , - * , - * ) - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def pickleAsTasty() = { - val pickleQuote = PickledQuotes.pickleQuote(body) - val pickledQuoteStrings = pickleQuote match - case x :: Nil => Literal(Constant(x)) - case xs => liftList(xs.map(x => Literal(Constant(x))), defn.StringType) - - // TODO split holes earlier into types and terms. This all holes in each category can have consecutive indices - val (typeSplices, termSplices) = splices.zipWithIndex.partition { case (splice, _) => - splice.tpe match - case defn.FunctionOf(_, res, _, _) => res.typeSymbol == defn.QuotedTypeClass - } - - // This and all closures in typeSplices are removed by the BetaReduce phase - val typeHoles = - if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible - else - Lambda( - MethodType( - List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType)), - defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)), - args => { - val cases = typeSplices.map { case (splice, idx) => - CaseDef(Literal(Constant(idx)), EmptyTree, splice.select(nme.apply).appliedTo(args(1))) - } - Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) - } - ) - - // This and all closures in termSplices are removed by the BetaReduce phase - val termHoles = - if termSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible - else - Lambda( - MethodType( - List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType), defn.QuotesClass.typeRef), - defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), - args => { - val cases = termSplices.map { case (splice, idx) => - val defn.FunctionOf(_, defn.FunctionOf(qctxType :: _, _, _, _), _, _) = splice.tpe - val rhs = splice.select(nme.apply).appliedTo(args(1)).select(nme.apply).appliedTo(args(2).asInstance(qctxType)) - CaseDef(Literal(Constant(idx)), EmptyTree, rhs) - } - Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) - } - ) - - val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass - val quotedType = quoteClass.typeRef.appliedTo(originalTp) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) - def callUnpickle(ts: List[Tree]) = { - val qctx = ts.head.asInstance(defn.QuoteUnpicklerClass.typeRef) - val unpickleMeth = if isType then defn.QuoteUnpickler_unpickleType else defn.QuoteUnpickler_unpickleExpr - qctx.select(unpickleMeth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, typeHoles, termHoles) - } - Lambda(lambdaTpe, callUnpickle).withSpan(body.span) - } - - /** Encode quote using Reflection.TypeRepr.typeConstructorOf - * - * Generate the code - * ```scala - * qctx.reflect.TypeReprMethods.asType( - * qctx.reflect.TypeRepr.typeConstructorOf(classOf[]]) - * ).asInstanceOf[scala.quoted.Type[]] - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def taggedType() = - val typeType = defn.QuotedTypeClass.typeRef.appliedTo(body.tpe) - val classTree = TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil) - val reflect = quote.args.head.select("reflect".toTermName) - val typeRepr = reflect.select("TypeRepr".toTermName).select("typeConstructorOf".toTermName).appliedTo(classTree) - reflect.select("TypeReprMethods".toTermName).select("asType".toTermName).appliedTo(typeRepr).asInstance(typeType) - - if (isType) { - if (splices.isEmpty && body.symbol.isPrimitiveValueClass) taggedType() - else pickleAsTasty().select(nme.apply).appliedTo(quote.args.head) // TODO do not create lambda - } - else getLiteral(body) match { - case Some(lit) => pickleAsValue(lit) - case _ => pickleAsTasty() - } - } - - /** If inside a quote, split the body of the splice into a core and a list of embedded quotes - * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. - */ - protected def transformSplice(body: Tree, splice: Apply)(using Context): Tree = - if (level > 1) { - val body1 = nested(isQuote = false).transform(body)(using spliceContext) - cpy.Apply(splice)(splice.fun, body1 :: Nil) - } - else { - assert(level == 1, "unexpected top splice outside quote") - val (body1, quotes) = nested(isQuote = false).splitSplice(body)(using spliceContext) - val tpe = outer.nn.embedded.getHoleType(body, splice) - val hole = makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) - // We do not place add the inline marker for trees that where lifted as they come from the same file as their - // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. - // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. - // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). - if (outer.nn.embedded.isLiftedSymbol(body.symbol)) hole - else Inlined(EmptyTree, Nil, hole).withSpan(splice.span) - } - - /** If inside a quote, split the body of the splice into a core and a list of embedded quotes - * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. - */ - protected def transformSpliceType(body: Tree, splice: Select)(using Context): Tree = - if level > 1 then - val body1 = nested(isQuote = false).transform(body)(using spliceContext) - cpy.Select(splice)(body1, splice.name) - else if level == 1 then - val (body1, quotes) = nested(isQuote = false).splitSplice(body)(using spliceContext) - val tpe = outer.nn.embedded.getHoleType(body, splice) - makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) - else - splice - - /** Transforms the contents of a nested splice - * Assuming - * '{ - * val x = ??? - * val y = ??? - * ${ ... '{ ... x .. y ... } ... } - * } - * then the spliced subexpression - * { ... '{ ... x ... y ... } ... } - * will be transformed to - * (args: Seq[Any]) => { - * val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * { ... '{ ... ${x$1} ... ${y$1} ... } ... } - * } - * - * See: `capture` - * - * At the same time register embedded trees `x` and `y` to place as arguments of the hole - * placed in the original code. - * '{ - * val x = ??? - * val y = ??? - * Hole(0 | x, y) - * } - */ - private def makeLambda(tree: Tree)(using Context): Tree = { - def body(arg: Tree)(using Context): Tree = { - var i = 0 - transformWithCapturer(tree)( - (captured: mutable.Map[Symbol, Tree]) => { - (tree: Tree) => { - def newCapture = { - val tpw = tree.tpe.widen match { - case tpw: MethodicType => tpw.toFunctionType(isJava = false) - case tpw => tpw - } - assert(tpw.isInstanceOf[ValueType]) - val argTpe = - if (tree.isType) defn.QuotedTypeClass.typeRef.appliedTo(tpw) - else defn.FunctionType(1, isContextual = true).appliedTo(defn.QuotesClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(tpw)) - val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).cast(argTpe) - val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg) - i += 1 - embedded.addTree(tree, capturedArg.symbol) - captured.put(tree.symbol, capturedArg) - capturedArg - } - val refSym = captured.getOrElseUpdate(tree.symbol, newCapture).symbol - ref(refSym).withSpan(tree.span) + private def makeHoles(tree: tpd.Tree)(using Context): (List[Tree], tpd.Tree) = + + class HoleContentExtractor extends Transformer: + private val contents = List.newBuilder[Tree] + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case tree @ Hole(isTerm, _, _, content, _) => + if !content.isEmpty then + contents += content + val holeType = + if isTerm then getTermHoleType(tree.tpe) else getTypeHoleType(tree.tpe) + val hole = cpy.Hole(tree)(content = EmptyTree, TypeTree(holeType)) + if isTerm then Inlined(EmptyTree, Nil, hole).withSpan(tree.span) else hole + case tree: DefTree => + val newAnnotations = tree.symbol.annotations.mapconserve { annot => + annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol))) } - } - ) + tree.symbol.annotations = newAnnotations + super.transform(tree) + case _ => + super.transform(tree).withType(mapAnnots(tree.tpe)) + + private def mapAnnots = new TypeMap { // TODO factor out duplicated logic in Splicing + override def apply(tp: Type): Type = { + tp match + case tp @ AnnotatedType(underlying, annot) => + val underlying1 = this(underlying) + derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) + case _ => mapOver(tp) + } } - /* Lambdas are generated outside the quote that is being reified (i.e. in outer.owner). - * In case the case that level == -1 the code is not in a quote, it is in an inline method, - * hence we should take that as owner directly. - */ - val lambdaOwner = if (level == -1) ctx.owner else outer.nn.owner - - val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe.widen) - val meth = newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe) - Closure(meth, tss => body(tss.head.head)(using ctx.withOwner(meth)).changeNonLocalOwners(meth)).withSpan(tree.span) - } - - private def transformWithCapturer(tree: Tree)(capturer: mutable.Map[Symbol, Tree] => Tree => Tree)(using Context): Tree = { - val captured = mutable.LinkedHashMap.empty[Symbol, Tree] - val captured2 = capturer(captured) - - outer.nn.localSymbols.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) - - val tree2 = transform(tree) - capturers --= outer.nn.localSymbols - - val captures = captured.result().valuesIterator.toList - if (captures.isEmpty) tree2 - else Block(captures, tree2) - } - - /** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */ - private def isCaptured(sym: Symbol, level: Int)(using Context): Boolean = - level == 1 && levelOf(sym) == 1 && capturers.contains(sym) - - /** Transform `tree` and return the resulting tree and all `embedded` quotes - * or splices as a pair. - */ - private def splitQuote(tree: Tree)(using Context): (Tree, List[Tree]) = { - val tree1 = stipTypeAnnotations(transform(tree)) - (tree1, embedded.getTrees) - } - - private def splitSplice(tree: Tree)(using Context): (Tree, List[Tree]) = { - val tree1 = makeLambda(tree) - (tree1, embedded.getTrees) - } - - private def stipTypeAnnotations(tree: Tree)(using Context): Tree = - new TreeTypeMap(typeMap = _.stripAnnots).apply(tree) - - /** Register `body` as an `embedded` quote or splice - * and return a hole with `splices` as arguments and the given type `tpe`. - */ - private def makeHole(isTermHole: Boolean, body: Tree, splices: List[Tree], tpe: Type)(using Context): Hole = { - val idx = embedded.addTree(body, NoSymbol) /** Remove references to local types that will not be defined in this quote */ - def getTypeHoleType(using Context) = new TypeMap() { + private def getTypeHoleType(using Context) = new TypeMap() { override def apply(tp: Type): Type = tp match case tp: TypeRef if tp.typeSymbol.isTypeSplice => apply(tp.dealias) @@ -457,71 +170,33 @@ class PickleQuotes extends MacroTransform { } /** Remove references to local types that will not be defined in this quote */ - def getTermHoleType(using Context) = new TypeMap() { + private def getTermHoleType(using Context) = new TypeMap() { override def apply(tp: Type): Type = tp match - case tp @ TypeRef(NoPrefix, _) if capturers.contains(tp.symbol) => + case tp @ TypeRef(NoPrefix, _) => // reference to term with a type defined in outer quote getTypeHoleType(tp) - case tp @ TermRef(NoPrefix, _) if capturers.contains(tp.symbol) => + case tp @ TermRef(NoPrefix, _) => // widen term refs to terms defined in outer quote apply(tp.widenTermRefExpr) case tp => mapOver(tp) } - val holeType = if isTermHole then getTermHoleType(tpe) else getTypeHoleType(tpe) + /** Get the contents of the transformed tree */ + def getContents() = + val res = contents.result + contents.clear() + res + end HoleContentExtractor - Hole(isTermHole, idx, splices).withType(holeType).asInstanceOf[Hole] - } + val holeMaker = new HoleContentExtractor + val newTree = holeMaker.transform(tree) + (holeMaker.getContents(), newTree) - override def transform(tree: Tree)(using Context): Tree = - if (tree.source != ctx.source && tree.source.exists) - transform(tree)(using ctx.withSource(tree.source)) - else reporting.trace(i"Reifier.transform $tree at $level", show = true) { - tree match { - case Apply(TypeApply(fn, (body: RefTree) :: Nil), _) - if fn.symbol == defn.QuotedTypeModule_of && isCaptured(body.symbol, level + 1) => - // Optimization: avoid the full conversion when capturing `X` with `x$1: Type[X$1]` - // in `Type.of[X]` to `Type.of[x$1.Underlying]` and go directly to `X$1` - capturers(body.symbol)(body) - case Apply(Select(Apply(TypeApply(fn,_), List(ref: RefTree)),nme.apply),List(quotes)) - if fn.symbol == defn.QuotedRuntime_exprQuote && isCaptured(ref.symbol, level + 1) => - // Optimization: avoid the full conversion when capturing `x` with `x$1: Expr[X]` - // in `'{x}` to `'{ ${x$1} }'` and go directly to `x$1` - capturers(ref.symbol)(ref).select(nme.apply).appliedTo(quotes) - case tree: RefTree if isCaptured(tree.symbol, level) => - val body = capturers(tree.symbol).apply(tree) - if (tree.isType) - transformSpliceType(body, body.select(tpnme.Underlying)) - else - val splice = ref(defn.QuotedRuntime_exprSplice).appliedToType(tree.tpe).appliedTo(body) - transformSplice(body, splice) - - case tree: DefDef if tree.symbol.is(Macro) && level == 0 => - // Shrink size of the tree. The methods have already been inlined. - // TODO move to FirstTransform to trigger even without quotes - cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) - - case tree: DefTree if level >= 1 => - val newAnnotations = tree.symbol.annotations.mapconserve { annot => - val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol)) - if (annot.tree == newAnnotTree) annot - else ConcreteAnnotation(newAnnotTree) - } - tree.symbol.annotations = newAnnotations - super.transform(tree) - case _ => - super.transform(tree) - } - } - private def liftList(list: List[Tree], tpe: Type)(using Context): Tree = - list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => - acc.select("::".toTermName).appliedToType(tpe).appliedTo(x) - } - } -} + end makeHoles +} object PickleQuotes { import tpd._ @@ -529,34 +204,180 @@ object PickleQuotes { val name: String = "pickleQuotes" val description: String = "turn quoted trees into explicit run-time data structures" - def getLiteral(tree: tpd.Tree): Option[Literal] = tree match { - case tree: Literal => Some(tree) - case Block(Nil, e) => getLiteral(e) - case Inlined(_, Nil, e) => getLiteral(e) - case _ => None - } + def apply(quotes: Tree, body: Tree, contents: List[Tree], originalTp: Type, isType: Boolean)(using Context) = { + /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ + object reflect extends ReifiedReflect { + val quotesTree = quotes + } - class Embedded(trees: mutable.ListBuffer[tpd.Tree] = mutable.ListBuffer.empty, map: mutable.Map[Symbol, tpd.Tree] = mutable.Map.empty) { - /** Adds the tree and returns it's index */ - def addTree(tree: tpd.Tree, liftedSym: Symbol): Int = { - trees += tree - if (liftedSym ne NoSymbol) - map.put(liftedSym, tree) - trees.length - 1 + /** Encode quote using Reflection.Literal + * + * Generate the code + * ```scala + * quotes => quotes.reflect.TreeMethods.asExpr( + * quotes.reflect.Literal.apply(x$1.reflect.Constant..apply()) + * ).asInstanceOf[scala.quoted.Expr[]] + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def pickleAsLiteral(lit: Literal) = { + val typeName = body.tpe.typeSymbol.name + val literalValue = + if lit.const.tag == Constants.NullTag || lit.const.tag == Constants.UnitTag then Nil + else List(body) + val constModule = lit.const.tag match + case Constants.BooleanTag => defn. Quotes_reflect_BooleanConstant + case Constants.ByteTag => defn. Quotes_reflect_ByteConstant + case Constants.ShortTag => defn. Quotes_reflect_ShortConstant + case Constants.IntTag => defn. Quotes_reflect_IntConstant + case Constants.LongTag => defn. Quotes_reflect_LongConstant + case Constants.FloatTag => defn. Quotes_reflect_FloatConstant + case Constants.DoubleTag => defn. Quotes_reflect_DoubleConstant + case Constants.CharTag => defn. Quotes_reflect_CharConstant + case Constants.StringTag => defn. Quotes_reflect_StringConstant + case Constants.UnitTag => defn. Quotes_reflect_UnitConstant + case Constants.NullTag => defn. Quotes_reflect_NullConstant + case Constants.ClazzTag => defn. Quotes_reflect_ClassOfConstant + reflect.asExpr(body.tpe) { + reflect.Literal { + reflect.self + .select(constModule) + .select(nme.apply) + .appliedToTermArgs(literalValue) + } + } } - /** Type used for the hole that will replace this splice */ - def getHoleType(body: tpd.Tree, splice: tpd.Tree)(using Context): Type = - // For most expressions the splice.tpe but there are some types that are lost by lifting - // that can be recoverd from the original tree. Currently the cases are: - // * Method types: the splice represents a method reference - map.get(body.symbol).map(_.tpe.widen).getOrElse(splice.tpe) + /** Encode quote using Reflection.Literal + * + * Generate the code + * ```scala + * quotes => scala.quoted.ToExpr.{BooleanToExpr,ShortToExpr, ...}.apply()(quotes) + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def liftedValue(lit: Literal, lifter: Symbol) = + val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) + ref(lifter).appliedToType(originalTp).select(nme.apply).appliedTo(lit).appliedTo(quotes) + + def pickleAsValue(lit: Literal) = { + // TODO should all constants be pickled as Literals? + // Should examine the generated bytecode size to decide and performance + lit.const.tag match { + case Constants.NullTag => pickleAsLiteral(lit) + case Constants.UnitTag => pickleAsLiteral(lit) + case Constants.BooleanTag => liftedValue(lit, defn.ToExprModule_BooleanToExpr) + case Constants.ByteTag => liftedValue(lit, defn.ToExprModule_ByteToExpr) + case Constants.ShortTag => liftedValue(lit, defn.ToExprModule_ShortToExpr) + case Constants.IntTag => liftedValue(lit, defn.ToExprModule_IntToExpr) + case Constants.LongTag => liftedValue(lit, defn.ToExprModule_LongToExpr) + case Constants.FloatTag => liftedValue(lit, defn.ToExprModule_FloatToExpr) + case Constants.DoubleTag => liftedValue(lit, defn.ToExprModule_DoubleToExpr) + case Constants.CharTag => liftedValue(lit, defn.ToExprModule_CharToExpr) + case Constants.StringTag => liftedValue(lit, defn.ToExprModule_StringToExpr) + } + } - def isLiftedSymbol(sym: Symbol)(using Context): Boolean = map.contains(sym) + /** Encode quote using QuoteUnpickler.{unpickleExpr, unpickleType} + * + * Generate the code + * ```scala + * quotes => quotes.asInstanceOf[QuoteUnpickler].[]( + * , + * , + * , + * ) + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def pickleAsTasty() = { + val pickleQuote = PickledQuotes.pickleQuote(body) + val pickledQuoteStrings = pickleQuote match + case x :: Nil => Literal(Constant(x)) + case xs => tpd.mkList(xs.map(x => Literal(Constant(x))), TypeTree(defn.StringType)) + + // TODO split holes earlier into types and terms. This all holes in each category can have consecutive indices + val (typeSplices, termSplices) = contents.zipWithIndex.partition { + _._1.tpe.derivesFrom(defn.QuotedTypeClass) + } - /** Get the list of embedded trees */ - def getTrees: List[tpd.Tree] = trees.toList + // This and all closures in typeSplices are removed by the BetaReduce phase + val types = + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else SeqLiteral(typeSplices.map(_._1), TypeTree(defn.QuotedTypeClass.typeRef.appliedTo(WildcardType))) - override def toString: String = s"Embedded($trees, $map)" + // This and all closures in termSplices are removed by the BetaReduce phase + val termHoles = + if termSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else + Lambda( + MethodType( + List(nme.idx, nme.contents, nme.quotes).map(name => UniqueName.fresh(name).toTermName), + List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType), defn.QuotesClass.typeRef), + defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), + args => + val cases = termSplices.map { case (splice, idx) => + val defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _, _), _, _) = splice.tpe + val rhs = { + val spliceArgs = argTypes.zipWithIndex.map { (argType, i) => + args(1).select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argType) + } + val Block(List(ddef: DefDef), _) = splice + // TODO: beta reduce inner closure? Or wait until BetaReduce phase? + BetaReduce(ddef, spliceArgs).select(nme.apply).appliedTo(args(2).asInstance(quotesType)) + } + CaseDef(Literal(Constant(idx)), EmptyTree, rhs) + } + cases match + case CaseDef(_, _, rhs) :: Nil => rhs + case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + ) + + val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass + val quotedType = quoteClass.typeRef.appliedTo(originalTp) + val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) + val unpickleMeth = + if isType then defn.QuoteUnpickler_unpickleTypeV2 + else defn.QuoteUnpickler_unpickleExprV2 + val unpickleArgs = + if isType then List(pickledQuoteStrings, types) + else List(pickledQuoteStrings, types, termHoles) + quotes + .asInstance(defn.QuoteUnpicklerClass.typeRef) + .select(unpickleMeth).appliedToType(originalTp) + .appliedToArgs(unpickleArgs).withSpan(body.span) + } + + /** Encode quote using Reflection.TypeRepr.typeConstructorOf + * + * Generate the code + * ```scala + * quotes.reflect.TypeReprMethods.asType( + * quotes.reflect.TypeRepr.typeConstructorOf(classOf[]]) + * ).asInstanceOf[scala.quoted.Type[]] + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def taggedType() = + reflect.asType(body.tpe) { + reflect.TypeRepr_typeConstructorOf( + TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil) + ) + } + + def getLiteral(tree: tpd.Tree): Option[Literal] = tree match + case tree: Literal => Some(tree) + case Block(Nil, e) => getLiteral(e) + case Inlined(_, Nil, e) => getLiteral(e) + case _ => None + + if (isType) then + if contents.isEmpty && body.symbol.isPrimitiveValueClass then taggedType() + else pickleAsTasty() + else + getLiteral(body) match + case Some(lit) => pickleAsValue(lit) + case _ => pickleAsTasty() } + } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3c6217cf5171..1b3ff3f0dd7e 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -339,7 +339,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case tree: TypeApply => if tree.symbol.isQuote then ctx.compilationUnit.needsStaging = true - ctx.compilationUnit.needsQuotePickling = true if tree.symbol.is(Inline) then ctx.compilationUnit.needsInlining = true val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala new file mode 100644 index 000000000000..0d25053a64cb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala @@ -0,0 +1,110 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._ +import Flags._ +import Types._ +import Contexts._ +import Symbols._ +import SymUtils._ +import NameKinds._ +import dotty.tools.dotc.ast.tpd +import tpd._ + +import scala.collection.mutable +import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Inliner + +import scala.annotation.constructorOnly + +/** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ +trait ReifiedReflect: + + /** Stable reference to the instance of `scala.quoted.Quotes` */ + def quotesTree: Tree + + def self(using Context): Tree = + quotesTree.select(defn.Quotes_reflect) + + /** Create type for `quotes.reflect.Term` */ + def TermTpt(using Context) = + self.select(defn.Quotes_reflect_TermType) + + /** Create type for `quotes.reflect.TypeTree` */ + def TypeTreeTpt(using Context) = + self.select(defn.Quotes_reflect_TypeTreeType) + + /** Create tree for `quotes.reflect.Apply(, List(*))` */ + def Apply(fn: Tree, args: List[Tree])(using Context) = + val argTrees = tpd.mkList(args, TermTpt) + self.select(defn.Quotes_reflect_Apply) + .select(defn.Quotes_reflect_Apply_apply) + .appliedTo(fn, argTrees) + + /** Create tree for `quotes.reflect.TypeApply(, List(*))` */ + def TypeApply(fn: Tree, args: List[Tree])(using Context) = + val argTrees = tpd.mkList(args, TypeTreeTpt) + self.select(defn.Quotes_reflect_TypeApply) + .select(defn.Quotes_reflect_TypeApply_apply) + .appliedTo(fn, argTrees) + + /** Create tree for `quotes.reflect.Assing(, )` */ + def Assign(lhs: Tree, rhs: Tree)(using Context) = + self.select(defn.Quotes_reflect_Assign) + .select(defn.Quotes_reflect_Assign_apply) + .appliedTo(lhs, rhs) + + /** Create tree for `quotes.reflect.Inferred()` */ + def Inferred(typeTree: Tree)(using Context) = + self.select(defn.Quotes_reflect_Inferred) + .select(defn.Quotes_reflect_Inferred_apply) + .appliedTo(typeTree) + + /** Create tree for `quotes.reflect.Literal()` */ + def Literal(constant: Tree)(using Context) = + self.select(defn.Quotes_reflect_Literal) + .select(defn.Quotes_reflect_Literal_apply) + .appliedTo(constant) + + /** Create tree for `quotes.reflect.TypeRepr.of(Type.of[](quotes))` */ + def TypeReprOf(tpe: Type)(using Context) = + self.select(defn.Quotes_reflect_TypeRepr) + .select(defn.Quotes_reflect_TypeRepr_of) + .appliedToType(tpe) + .appliedTo( + ref(defn.QuotedTypeModule_of) + .appliedToType(tpe) + .appliedTo(quotesTree) + ) + + /** Create tree for `quotes.reflect.TypeRepr.typeConstructorOf()` */ + def TypeRepr_typeConstructorOf(classTree: Tree)(using Context) = + self.select(defn.Quotes_reflect_TypeRepr) + .select(defn.Quotes_reflect_TypeRepr_typeConstructorOf) + .appliedTo(classTree) + + /** Create tree for `quotes.reflect.asTerm()` */ + def asTerm(expr: Tree)(using Context) = + self.select(defn.Quotes_reflect_asTerm) + .appliedTo(expr) + + /** Create tree for `quotes.reflect.TypeReprMethods.asType()` */ + def asType(tpe: Type)(typeRepr: Tree)(using Context) = + self.select(defn.Quotes_reflect_TypeReprMethods) + .select(defn.Quotes_reflect_TypeReprMethods_asType) + .appliedTo(typeRepr) + .asInstance(defn.QuotedTypeClass.typeRef.appliedTo(tpe)) + + /** Create tree for `quotes.reflect.TreeMethods.asExpr().asInstanceOf[]` */ + def asExpr(tpe: Type)(term: Tree)(using Context) = + self.select(defn.Quotes_reflect_TreeMethods) + .select(defn.Quotes_reflect_TreeMethods_asExpr) + .appliedTo(term) + .asInstance(defn.QuotedExprClass.typeRef.appliedTo(tpe)) + +end ReifiedReflect diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 47422512823e..621935df6bb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -118,7 +118,10 @@ object ResolveSuper { report.error(IllegalSuperAccessor(base, memberName, targetName, acc, accTp, other.symbol, otherTp), base.srcPos) bcs = bcs.tail } - assert(sym.exists, i"cannot rebind $acc, ${acc.targetName} $memberName") - sym + sym.orElse { + val originalName = acc.name.asTermName.originalOfSuperAccessorName + report.error(em"Member method ${originalName.debugString} of mixin ${acc.owner} is missing a concrete super implementation in $base.", base.srcPos) + acc + } } } diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala new file mode 100644 index 000000000000..fe0c233173b7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -0,0 +1,392 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._ +import Flags._ +import Types._ +import Contexts._ +import Symbols._ +import Constants._ +import ast.Trees._ +import ast.{TreeTypeMap, untpd} +import util.Spans._ +import SymUtils._ +import NameKinds._ +import dotty.tools.dotc.ast.tpd +import StagingContext._ + +import scala.collection.mutable +import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.config.ScalaRelease.* + +import scala.annotation.constructorOnly + +object Splicing: + val name: String = "splicing" + +/** Transforms level 1 splices into holes. To do so it transforms the contents of the splice into + * a lambda that receives all cross-quote references. + * + * Cross-quote reference is a reference to a definition that is not defined in the current quote. + * Those references appear in quotes that are nested in a splice. + * + * After this phase we have the invariant where all splices have the following shape + * ``` + * {{{ | | * | (*) => }}} + * ``` + * where `` does not contain any free references to quoted definitions and `*` + * contains the quotes with references to all cross-quote references. There are some special rules + * for references in the LHS of assignments and cross-quote method references. + * + * In the following code example `x1` and `x2` are cross-quote references. + * ``` + * '{ ... + * val x1: T1 = ??? + * val x2: T2 = ??? + * ${ (q: Quotes) ?=> f('{ g(x1, x2) }) }: T3 + * } + * ``` + * + * This phase identifies cross-quote references such as `x1` and replaces it with an `${x1$}`. + * All cross-quote arguments are directly applied in the lambda. + * + * ``` + * '{ ... + * val x1: T1 = ??? + * val x2: T2 = ??? + * {{{ 0 | T3 | x1, x2 | + * (x1$: Expr[T1], x2$: Expr[T2]) => // body of this lambda does not contain references to x1 or x2 + * (q: Quotes) ?=> f('{ g(${x1$}, ${x2$}) }) + * + * }}} + * } + * ``` + * + * and then performs the same transformation on `'{ g(${x1$}, ${x2$}) }`. + * + */ +class Splicing extends MacroTransform: + import tpd._ + + override def phaseName: String = Splicing.name + + override def run(using Context): Unit = + if ctx.compilationUnit.needsStaging then + super.run(using freshStagingContext) + + protected def newTransformer(using Context): Transformer = Level0QuoteTransformer + + /** Transforms all quotes at level 0 using the `QuoteTransformer` */ + private object Level0QuoteTransformer extends Transformer: + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + assert(level == 0) + tree match + case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + QuoteTransformer().transform(tree) + case TypeApply(_, _) if tree.symbol == defn.QuotedTypeModule_of => + QuoteTransformer().transform(tree) + case tree: DefDef if tree.symbol.is(Inline) => + // Quotes in inlined methods are only pickled after they are inlined. + tree + case _ => + super.transform(tree) + end Level0QuoteTransformer + + + /** Transforms all direct splices in the current quote and replace them with holes. */ + private class QuoteTransformer() extends Transformer: + /** Set of definitions in the current quote */ + private val quotedDefs = mutable.Set.empty[Symbol] + + /** Number of holes created in this quote. Used for indexing holes. */ + private var numHoles = 0 + + /** Mapping from the term symbol of a `Type[T]` to it's hole. Used to deduplicate type holes. */ + private val typeHoles = mutable.Map.empty[Symbol, Hole] + + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case Apply(fn, List(splicedCode)) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => + if level > 1 then + val splicedCode1 = super.transform(splicedCode)(using spliceContext) + cpy.Apply(tree)(fn, List(splicedCode1)) + else + val holeIdx = numHoles + numHoles += 1 + val splicer = SpliceTransformer(ctx.owner, quotedDefs.contains) + val newSplicedCode1 = splicer.transformSplice(splicedCode, tree.tpe, holeIdx)(using spliceContext) + val newSplicedCode2 = Level0QuoteTransformer.transform(newSplicedCode1)(using spliceContext) + newSplicedCode2 + case tree: TypeDef if tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + val tp @ TypeRef(qual: TermRef, _) = tree.rhs.tpe.hiBound + quotedDefs += tree.symbol + val hole = typeHoles.get(qual.symbol) match + case Some (hole) => cpy.Hole(hole)(content = EmptyTree) + case None => + val holeIdx = numHoles + numHoles += 1 + val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) + typeHoles.put(qual.symbol, hole) + hole + cpy.TypeDef(tree)(rhs = hole) + case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + super.transform(tree)(using quoteContext) + case _: Template => + for sym <- tree.symbol.owner.info.decls do + quotedDefs += sym + super.transform(tree) + case tree: DefTree => + quotedDefs += tree.symbol + transformAnnotations(tree) + super.transform(tree) + case _: TypeTree => + super.transform(tree).withType(transformAnnotTrees(tree.tpe)) + case _ => + super.transform(tree) + + private def transformAnnotations(tree: DefTree)(using Context): Unit = + tree.symbol.annotations = tree.symbol.annotations.mapconserve { annot => + val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol)) + if (annot.tree == newAnnotTree) annot + else ConcreteAnnotation(newAnnotTree) + } + + /** Transform trees within annotations */ + private def transformAnnotTrees(using Context) = new TypeMap { + override def apply(tp: Type): Type = { + tp match + case tp @ AnnotatedType(underlying, annot) => + val underlying1 = this(underlying) + derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) + case _ => mapOver(tp) + } + } + + end QuoteTransformer + + /** Transforms a splice at level 1 into a hole + * + * Finds all terms and types that are defined in the current quote and used within this splice. + * The resulting hole will contain all of these terms and types as arguments. + * Note that these captured variables are stage correct. + * + * For a `x` of type `T1` and a type `X` defined in the current quote + * ```scala + * ${ (using Quotes) ?=> {... x ... X ...} }: T2 + * ``` + * is transformed into + * ```scala + * {{{ | T2 | x, X | (x$1: Expr[T1], X$1: Type[X]) => (using Quotes) ?=> {... ${x$1} ... X$1.Underlying ...} }}} + * ``` + */ + private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: + private var refBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] + /** Reference to the `Quotes` instance of the current level 1 splice */ + private var quotes: Tree | Null = null // TODO: add to the context + private var healedTypes: PCPCheckAndHeal.QuoteTypeTags | Null = null // TODO: add to the context + + def transformSplice(tree: tpd.Tree, tpe: Type, holeIdx: Int)(using Context): tpd.Tree = + assert(level == 0) + val newTree = transform(tree) + val (refs, bindings) = refBindingMap.values.toList.unzip + val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr) + val methType = MethodType(bindingsTypes, newTree.tpe) + val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methType) + val ddef = DefDef(meth, List(bindings), newTree.tpe, newTree.changeOwner(ctx.owner, meth)) + val fnType = defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) + val closure = Block(ddef :: Nil, Closure(Nil, ref(meth), TypeTree(fnType))) + tpd.Hole(true, holeIdx, refs, closure, TypeTree(tpe)) + + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case tree: RefTree => + if tree.isTerm then + if isCaptured(tree.symbol) then + val tpe = tree.tpe.widenTermRefExpr match { + case tpw: MethodicType => tpw.toFunctionType(isJava = false) + case tpw => tpw + } + spliced(tpe)(capturedTerm(tree)) + else super.transform(tree) + else // tree.isType then + if containsCapturedType(tree.tpe) then + if level >= 1 then getTagRefFor(tree) + else + // Dealias references to captured types + TypeTree(tree.tpe.dealias) + else super.transform(tree) + case tree: TypeTree => + if containsCapturedType(tree.tpe) && level >= 1 then getTagRefFor(tree) + else tree + case tree @ Assign(lhs: RefTree, rhs) => + if isCaptured(lhs.symbol) then transformSplicedAssign(tree) + else super.transform(tree) + case Apply(fn, args) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => + val newArgs = args.mapConserve(arg => transform(arg)(using spliceContext)) + cpy.Apply(tree)(fn, newArgs) + case Apply(sel @ Select(app @ Apply(fn, args),nme.apply), quotesArgs) + if fn.symbol == defn.QuotedRuntime_exprQuote => + args match + case List(tree: RefTree) if isCaptured(tree.symbol) => + capturedTerm(tree) + case _ => + val newArgs = withCurrentQuote(quotesArgs.head) { + if level > 1 then args.mapConserve(arg => transform(arg)(using quoteContext)) + else args.mapConserve(arg => transformLevel0QuoteContent(arg)(using quoteContext)) + } + cpy.Apply(tree)(cpy.Select(sel)(cpy.Apply(app)(fn, newArgs), nme.apply), quotesArgs) + case Apply(TypeApply(_, List(tpt)), List(quotes)) + if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) => + ref(capturedType(tpt))(using ctx.withSource(tree.source)).withSpan(tree.span) + case CapturedApplication(fn, argss) => + transformCapturedApplication(tree, fn, argss) + case _ => + super.transform(tree) + + private def transformLevel0QuoteContent(tree: Tree)(using Context): Tree = + // transform and collect new healed types + val old = healedTypes + healedTypes = new PCPCheckAndHeal.QuoteTypeTags(tree.span) + val tree1 = transform(tree) + val newHealedTypes = healedTypes.nn.getTypeTags + healedTypes = old + // add new healed types to the current, merge with existing healed types if necessary + if newHealedTypes.isEmpty then tree1 + else tree1 match + case Block(stats @ (x :: _), expr) if x.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + Block(newHealedTypes ::: stats, expr) + case _ => + Block(newHealedTypes, tree1) + + class ArgsClause(val args: List[Tree]): + def isTerm: Boolean = args.isEmpty || args.head.isTerm + + private object CapturedApplication { + + /** Matches and application `f(...)` (possibly with several argument clauses) where `f` is captured */ + def unapply(tree: Tree)(using Context): Option[(RefTree, List[ArgsClause])] = tree match + case GenericApply(fn: RefTree, args) if isCaptured(fn.symbol) => + Some((fn, ArgsClause(args) :: Nil)) + case GenericApply(CapturedApplication(fn, argss), args) => + Some((fn, argss :+ ArgsClause(args))) + case _ => + None + } + + private def containsCapturedType(tpe: Type)(using Context): Boolean = + tpe.existsPart(t => isCaptured(t.typeSymbol) || isCaptured(t.termSymbol), StopAt.Static) + + /** Transform an assignment `x = e` with a captured `x` to + * `${ Assign(x$1.asTerm, '{e}.asTerm).asExpr.asInstanceOf[Expr[T]] }` + * + * Registers `x` as a captured variable in the hole and creates an `x$1` `Expr` reference to it. + */ + private def transformSplicedAssign(tree: Assign)(using Context): Tree = + spliced(tree.tpe) { + reflect.asExpr(tree.tpe)( + reflect.Assign( + reflect.asTerm(capturedTerm(tree.lhs)), + reflect.asTerm(quoted(tree.rhs)) + ) + ) + } + + /** Transform an application `f(a1, a2, ...)` with a captured `f` to + * `${ Apply(f$1.asTerm, List('{a1$}.asTerm, '{a2$}.asTerm, ...)).asExpr.asInstanceOf[Expr[T]] }` + * + * Registers `f` as a captured variable in the hole and creates an `f$1` `Expr` reference to it. + * + * It also handles cases with multiple argument clauses using nested `Apply`/`TypeApply`. + */ + private def transformCapturedApplication(tree: Tree, fn: RefTree, argss: List[ArgsClause])(using Context): Tree = + spliced(tree.tpe) { + def TermList(args: List[Tree]): List[Tree] = + args.map(arg => reflect.asTerm(quoted(transform(arg)(using spliceContext)))) + def TypeTreeList(args: List[Tree]): List[Tree] = + args.map(arg => reflect.Inferred(reflect.TypeReprOf(transform(arg)(using spliceContext).tpe))) + reflect.asExpr(tree.tpe) { + argss.foldLeft[Tree](reflect.asTerm(capturedTerm(fn, defn.AnyType))) { (acc, clause) => + if clause.isTerm then reflect.Apply(acc, TermList(clause.args)) + else reflect.TypeApply(acc, TypeTreeList(clause.args)) + } + } + } + + private def capturedTerm(tree: Tree)(using Context): Tree = + val tpe = tree.tpe.widenTermRefExpr match + case tpw: MethodicType => tpw.toFunctionType(isJava = false) + case tpw => tpw + capturedTerm(tree, tpe) + + private def capturedTerm(tree: Tree, tpe: Type)(using Context): Tree = + def newBinding = newSymbol( + spliceOwner, + UniqueName.fresh(tree.symbol.name.toTermName).toTermName, + Param, + defn.QuotedExprClass.typeRef.appliedTo(tpe), + ) + val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2 + ref(bindingSym) + + private def capturedType(tree: Tree)(using Context): Symbol = + val tpe = tree.tpe.widenTermRefExpr + def newBinding = newSymbol( + spliceOwner, + UniqueName.fresh(nme.Type).toTermName, + Param, + defn.QuotedTypeClass.typeRef.appliedTo(tpe), + ) + val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (TypeTree(tree.tpe), newBinding))._2 + bindingSym + + private def getTagRefFor(tree: Tree)(using Context): Tree = + val capturedTypeSym = capturedType(tree) + TypeTree(healedTypes.nn.getTagRef(capturedTypeSym.termRef)) + + private def withCurrentQuote[T](newQuotes: Tree)(body: => T)(using Context): T = + if level == 0 then + val savedQuotes = quotes + quotes = newQuotes + try body + finally quotes = savedQuotes + else body + + private def spliced(tpe: Type)(body: Context ?=> Tree)(using Context): Tree = + val exprTpe = defn.QuotedExprClass.typeRef.appliedTo(tpe) + val closure = + val methTpe = ContextualMethodType(List(defn.QuotesClass.typeRef), exprTpe) + val meth = newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, methTpe) + Closure(meth, argss => { + withCurrentQuote(argss.head.head) { + body(using ctx.withOwner(meth)).changeOwner(ctx.owner, meth) + } + }) + ref(defn.QuotedRuntime_exprNestedSplice) + .appliedToType(tpe) + .appliedTo(Literal(Constant(null))) // Dropped when creating the Hole that contains it + .appliedTo(closure) + + private def quoted(expr: Tree)(using Context): Tree = + val tpe = expr.tpe.widenTermRefExpr + ref(defn.QuotedRuntime_exprQuote) + .appliedToType(tpe) + .appliedTo(expr) + .select(nme.apply) + .appliedTo(quotes.nn) + + /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ + private object reflect extends ReifiedReflect { + def quotesTree = quotes.nn + } + + end SpliceTransformer + +end Splicing diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index a327cff83950..1de050a9a6c1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -31,7 +31,7 @@ class Staging extends MacroTransform { override def allowsImplicitSearch: Boolean = true override def checkPostCondition(tree: Tree)(using Context): Unit = - if (ctx.phase <= pickleQuotesPhase) { + if (ctx.phase <= splicingPhase) { // Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald tree match { case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 2aee452070f9..71b66c3d0da6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -286,23 +286,11 @@ class TailRec extends MiniPhase { def yesTailTransform(tree: Tree)(using Context): Tree = transform(tree, tailPosition = true) - /** If not in tail position a tree traversal may not be needed. - * - * A recursive call may still be in tail position if within the return - * expression of a labeled block. - * A tree traversal may also be needed to report a failure to transform - * a recursive call of a @tailrec annotated method (i.e. `isMandatory`). - */ - private def isTraversalNeeded = - isMandatory || tailPositionLabeledSyms.size > 0 - def noTailTransform(tree: Tree)(using Context): Tree = - if (isTraversalNeeded) transform(tree, tailPosition = false) - else tree + transform(tree, tailPosition = false) def noTailTransforms[Tr <: Tree](trees: List[Tr])(using Context): List[Tr] = - if (isTraversalNeeded) trees.mapConserve(noTailTransform).asInstanceOf[List[Tr]] - else trees + trees.mapConserve(noTailTransform).asInstanceOf[List[Tr]] override def transform(tree: Tree)(using Context): Tree = { /* Rewrite an Apply to be considered for tail call transformation. */ @@ -456,7 +444,7 @@ class TailRec extends MiniPhase { case Return(expr, from) => val fromSym = from.symbol - val inTailPosition = fromSym.is(Label) && tailPositionLabeledSyms.contains(fromSym) + val inTailPosition = !fromSym.is(Label) || tailPositionLabeledSyms.contains(fromSym) cpy.Return(tree)(transform(expr, inTailPosition), from) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index bd3f4f44984b..3bd3050ee8f1 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -130,6 +130,7 @@ class TreeChecker extends Phase with SymTransformer { assert(ctx.typerState.constraint.domainLambdas.isEmpty, i"non-empty constraint at end of $fusedPhase: ${ctx.typerState.constraint}, ownedVars = ${ctx.typerState.ownedVars.toList}%, %") assertSelectWrapsNew(ctx.compilationUnit.tpdTree) + TreeNodeChecker.traverse(ctx.compilationUnit.tpdTree) } val checkingCtx = ctx @@ -573,6 +574,37 @@ class TreeChecker extends Phase with SymTransformer { else super.typedPackageDef(tree) + override def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = { + val tree1 @ Hole(isTermHole, _, args, content, tpt) = super.typedHole(tree, pt) + + // Check result type of the hole + if isTermHole then assert(tpt.typeOpt <:< pt) + else assert(tpt.typeOpt =:= pt) + + // Check that the types of the args conform to the types of the contents of the hole + val argQuotedTypes = args.map { arg => + if arg.isTerm then + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpe) + else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt) + } + val expectedResultType = + if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt) + else defn.QuotedTypeClass.typeRef.appliedTo(tpt.typeOpt) + val contextualResult = + defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) + val expectedContentType = + defn.FunctionOf(argQuotedTypes, contextualResult) + assert(content.typeOpt =:= expectedContentType) + + tree1 + } + override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree = tree @@ -646,4 +678,25 @@ object TreeChecker { tp } }.apply(tp0) + + /** Run some additional checks on the nodes of the trees. Specifically: + * + * - TypeTree can only appear in TypeApply args, New, Typed tpt, Closure + * tpt, SeqLiteral elemtpt, ValDef tpt, DefDef tpt, and TypeDef rhs. + */ + object TreeNodeChecker extends untpd.TreeTraverser: + import untpd._ + def traverse(tree: Tree)(using Context) = tree match + case t: TypeTree => assert(assertion = false, i"TypeTree not expected: $t") + case t @ TypeApply(fun, _targs) => traverse(fun) + case t @ New(_tpt) => + case t @ Typed(expr, _tpt) => traverse(expr) + case t @ Closure(env, meth, _tpt) => traverse(env); traverse(meth) + case t @ SeqLiteral(elems, _elemtpt) => traverse(elems) + case t @ ValDef(_, _tpt, _) => traverse(t.rhs) + case t @ DefDef(_, paramss, _tpt, _) => for params <- paramss do traverse(params); traverse(t.rhs) + case t @ TypeDef(_, _rhs) => + case t @ Template(constr, parents, self, _) => traverse(constr); traverse(parents); traverse(self); traverse(t.body) + case t => traverseChildren(t) + end traverse } diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index c2367e6bfde0..fe06e90340a7 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -151,6 +151,7 @@ object TypeTestsCasts { case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) case AnnotatedType(t, _) => recur(X, t) case tp2: RefinedType => recur(X, tp2.parent) && TypeComparer.hasMatchingMember(tp2.refinedName, X, tp2) + case tp2: RecType => recur(X, tp2.parent) case _ => true }) diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala index 2d9934d5526c..97bdc138f571 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala @@ -14,7 +14,8 @@ import Contexts._ import Decorators._ import DenotTransformers._ import Flags._ -import NameKinds.DefaultGetterName +import NameKinds.{DefaultGetterName, ModuleClassName} +import NameOps._ import StdNames._ import Symbols._ import SymUtils._ @@ -559,9 +560,14 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP case Some(annot) if annot.symbol == jsdefn.JSGlobalAnnot => checkJSGlobalLiteral(annot) val pathName = annot.argumentConstantString(0).getOrElse { - if ((enclosingOwner is OwnerKind.ScalaMod) && !sym.owner.isPackageObject) { + val symTermName = sym.name.exclude(NameKinds.ModuleClassName).toTermName + if (symTermName == nme.apply) { report.error( - "Native JS members inside non-native objects must have an explicit name in @JSGlobal", + "Native JS definitions named 'apply' must have an explicit name in @JSGlobal", + annot.tree) + } else if (symTermName.isSetterName) { + report.error( + "Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal", annot.tree) } sym.defaultJSName @@ -570,6 +576,18 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP case Some(annot) if annot.symbol == jsdefn.JSImportAnnot => checkJSImportLiteral(annot) + if (annot.arguments.sizeIs < 2) { + val symTermName = sym.name.exclude(NameKinds.ModuleClassName).toTermName + if (symTermName == nme.apply) { + report.error( + "Native JS definitions named 'apply' must have an explicit name in @JSImport", + annot.tree) + } else if (symTermName.isSetterName) { + report.error( + "Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport", + annot.tree) + } + } annot.argumentConstantString(2).foreach { globalPathName => checkGlobalRefPath(globalPathName) } @@ -1107,18 +1125,19 @@ object PrepJSInterop { */ private def checkJSImportLiteral(annot: Annotation)(using Context): Unit = { val args = annot.arguments - assert(args.size == 2 || args.size == 3, - i"@JSImport annotation $annot does not have exactly 2 or 3 arguments") + val argCount = args.size + assert(argCount >= 1 && argCount <= 3, + i"@JSImport annotation $annot does not have between 1 and 3 arguments") val firstArgIsValid = annot.argumentConstantString(0).isDefined if (!firstArgIsValid) report.error("The first argument to @JSImport must be a literal string.", args.head) - val secondArgIsValid = annot.argumentConstantString(1).isDefined || args(1).symbol == jsdefn.JSImportNamespaceModule + val secondArgIsValid = argCount < 2 || annot.argumentConstantString(1).isDefined || args(1).symbol == jsdefn.JSImportNamespaceModule if (!secondArgIsValid) report.error("The second argument to @JSImport must be literal string or the JSImport.Namespace object.", args(1)) - val thirdArgIsValid = args.size < 3 || annot.argumentConstantString(2).isDefined + val thirdArgIsValid = argCount < 3 || annot.argumentConstantString(2).isDefined if (!thirdArgIsValid) report.error("The third argument to @JSImport, when present, must be a literal string.", args(2)) } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a43d32e591ee..d80d4f366636 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1378,7 +1378,7 @@ trait Applications extends Compatibility { val unapplyPatterns = bunchedArgs.lazyZip(argTypes) map (typed(_, _)) val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType) unapp.println(s"unapply patterns = $unapplyPatterns") - if ((ownType eq selType) || ownType.isError) result + if (ownType.stripped eq selType.stripped) || ownType.isError then result else tryWithTypeTest(Typed(result, TypeTree(ownType)), selType) case tp => val unapplyErr = if (tp.isError) unapplyFn else notAnExtractor(unapplyFn) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 6e8fa8b9c9d8..1e597e42d2a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -473,8 +473,7 @@ object Checking { if sym.isInlineMethod && !sym.is(Deferred) && sym.allOverriddenSymbols.nonEmpty then checkInlineOverrideParameters(sym) if (sym.is(Implicit)) { - if (sym.owner.is(Package)) - fail(TopLevelCantBeImplicit(sym)) + assert(!sym.owner.is(Package), s"top-level implicit $sym should be wrapped by a package after typer") if sym.isType && (!sym.isClass || sym.is(Trait)) then fail(TypesAndTraitsCantBeImplicit()) } @@ -1197,7 +1196,7 @@ trait Checking { case _: TypeTree => case _ => if tree.tpe.typeParams.nonEmpty then - val what = if tree.symbol.exists then tree.symbol else i"type $tree" + val what = if tree.symbol.exists then tree.symbol.show else i"type $tree" report.error(em"$what takes type parameters", tree.srcPos) /** Check that we are in an inline context (inside an inline method or in inline code) */ @@ -1351,12 +1350,13 @@ trait Checking { def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = !ctx.reporter.reportsErrorsFor { val annotCls = Annotations.annotClass(annot) + val concreteAnnot = Annotations.ConcreteAnnotation(annot) val pos = annot.srcPos - if (annotCls == defn.MainAnnot) { + if (annotCls == defn.MainAnnot || concreteAnnot.matches(defn.MainAnnotationClass)) { if (!sym.isRealMethod) - report.error(em"@main annotation cannot be applied to $sym", pos) + report.error(em"main annotation cannot be applied to $sym", pos) if (!sym.owner.is(Module) || !sym.owner.isStatic) - report.error(em"$sym cannot be a @main method since it cannot be accessed statically", pos) + report.error(em"$sym cannot be a main method since it cannot be accessed statically", pos) } // TODO: Add more checks here } diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index c0c6ede200ed..0d955651dbd9 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -11,6 +11,7 @@ import Symbols._ import Names._ import NameKinds.UniqueName import util.Spans._ +import util.Property import collection.mutable import Trees._ @@ -155,6 +156,27 @@ class LiftComplex extends Lifter { } object LiftComplex extends LiftComplex +/** Lift complex + lift the prefixes */ +object LiftCoverage extends LiftComplex { + + private val LiftEverything = new Property.Key[Boolean] + + private inline def liftEverything(using Context): Boolean = + ctx.property(LiftEverything).contains(true) + + private def liftEverythingContext(using Context): Context = + ctx.fresh.setProperty(LiftEverything, true) + + override def noLift(expr: tpd.Tree)(using Context) = + !liftEverything && super.noLift(expr) + + def liftForCoverage(defs: mutable.ListBuffer[tpd.Tree], tree: tpd.Apply)(using Context) = { + val liftedFun = liftApp(defs, tree.fun) + val liftedArgs = liftArgs(defs, tree.fun.tpe, tree.args)(using liftEverythingContext) + tpd.cpy.Apply(tree)(liftedFun, liftedArgs) + } +} + object LiftErased extends LiftComplex: override def isErased = true diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 67bc68c4a495..d097ed8dcc4e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1629,12 +1629,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case res => specializeEq(inlineIfNeeded(res)) } - if res.symbol == defn.QuotedRuntime_exprQuote then - ctx.compilationUnit.needsQuotePickling = true res override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + val tree1 = inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + if tree1.symbol.isQuote then + ctx.compilationUnit.needsStaging = true + tree1 override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = val tree1 = diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index aac0288aa771..07ac87d5455a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -24,6 +24,7 @@ import Inferencing._ import transform.ValueClasses._ import transform.TypeUtils._ import transform.SymUtils._ +import TypeErasure.erasure import reporting._ import config.Feature.sourceVersion import config.SourceVersion._ @@ -1185,7 +1186,8 @@ class Namer { typer: Typer => buf += ddef.withSpan(span) if hasDefaults then foreachDefaultGetterOf(sym.asTerm, - getter => addForwarder(getter.name.asTermName, getter, span)) + getter => addForwarder( + getter.name.asTermName, getter.asSeenFrom(path.tpe), span)) end addForwarder def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = @@ -1237,8 +1239,64 @@ class Namer { typer: Typer => addForwarders(sels1, sel.name :: seen) case _ => + /** Avoid a clash of export forwarder `forwarder` with other forwarders in `forwarders`. + * @return If `forwarder` clashes, a new leading forwarder and trailing forwarders list + * that avoids the clash according to the scheme described in `avoidClashes`. + * If there's no clash, the inputs as they are in a pair. + */ + def avoidClashWith(forwarder: tpd.DefDef, forwarders: List[tpd.MemberDef]): (tpd.DefDef, List[tpd.MemberDef]) = + def clashes(fwd1: Symbol, fwd2: Symbol) = + fwd1.targetName == fwd2.targetName + && erasure(fwd1.info).signature == erasure(fwd2.info).signature + + forwarders match + case forwarders @ ((forwarder1: tpd.DefDef) :: forwarders1) + if forwarder.name == forwarder1.name => + if clashes(forwarder.symbol, forwarder1.symbol) then + val alt1 = tpd.methPart(forwarder.rhs).tpe + val alt2 = tpd.methPart(forwarder1.rhs).tpe + val cmp = alt1 match + case alt1: TermRef => alt2 match + case alt2: TermRef => compare(alt1, alt2) + case _ => 0 + case _ => 0 + if cmp == 0 then + report.error( + ex"""Clashing exports: The exported + | ${forwarder.rhs.symbol}: ${alt1.widen} + |and ${forwarder1.rhs.symbol}: ${alt2.widen} + |have the same signature after erasure and overloading resolution could not disambiguate.""", + exp.srcPos) + avoidClashWith(if cmp < 0 then forwarder1 else forwarder, forwarders1) + else + val (forwarder2, forwarders2) = avoidClashWith(forwarder, forwarders1) + (forwarder2, forwarders.derivedCons(forwarder1, forwarders2)) + case _ => + (forwarder, forwarders) + end avoidClashWith + + /** Avoid clashes of any two export forwarders in `forwarders`. + * A clash is if two forwarders f1 and f2 have the same name and signatures after erasure. + * We try to avoid a clash by dropping one of f1 and f2, keeping the one whose right hand + * side reference would be preferred by overloading resolution. + * If neither of f1 or f2 is preferred over the other, report an error. + * + * The idea is that this simulates the hypothetical case where export forwarders + * are not generated and we treat an export instead more like an import where we + * expand the use site reference. Test cases in {neg,pos}/i14699.scala. + * + * @pre Forwarders with the same name are consecutive in `forwarders`. + */ + def avoidClashes(forwarders: List[tpd.MemberDef]): List[tpd.MemberDef] = forwarders match + case forwarders @ (forwarder :: forwarders1) => + val (forwarder2, forwarders2) = forwarder match + case forwarder: tpd.DefDef => avoidClashWith(forwarder, forwarders1) + case _ => (forwarder, forwarders1) + forwarders.derivedCons(forwarder2, avoidClashes(forwarders2)) + case Nil => forwarders + addForwarders(selectors, Nil) - val forwarders = buf.toList + val forwarders = avoidClashes(buf.toList) exp.pushAttachment(ExportForwarders, forwarders) forwarders end exportForwarders diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index f8422ed20e21..4a9102df211d 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -187,6 +187,10 @@ trait QuotesAndSplices { using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx))) pat.select(tpnme.Underlying) + def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = + val tpt = typedType(tree.tpt) + assignType(tree, tpt) + private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit = if (level == 0 && !ctx.owner.ownersIterator.exists(_.is(Inline))) report.error("Splice ${...} outside quotes '{...} or inline method", tree.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index e8924b418c3f..80068d8632e6 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -536,6 +536,10 @@ trait TypeAssigner { def assignType(tree: untpd.PackageDef, pid: Tree)(using Context): PackageDef = tree.withType(pid.symbol.termRef) + + def assignType(tree: untpd.Hole, tpt: Tree)(using Context): Hole = + tree.withType(tpt.tpe) + } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ac8d6152812e..0af8ee8dc624 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1128,15 +1128,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if tree.isInline then checkInInlineContext("inline if", tree.srcPos) val cond1 = typed(tree.cond, defn.BooleanType) + def isIncomplete(tree: untpd.If): Boolean = tree.elsep match + case EmptyTree => true + case elsep: untpd.If => isIncomplete(elsep) + case _ => false + + val branchPt = if isIncomplete(tree) then defn.UnitType else pt.dropIfProto + val result = if tree.elsep.isEmpty then - val thenp1 = typed(tree.thenp, defn.UnitType)(using cond1.nullableContextIf(true)) + val thenp1 = typed(tree.thenp, branchPt)(using cond1.nullableContextIf(true)) val elsep1 = tpd.unitLiteral.withSpan(tree.span.endPos) cpy.If(tree)(cond1, thenp1, elsep1).withType(defn.UnitType) else val thenp1 :: elsep1 :: Nil = harmonic(harmonize, pt) { - val thenp0 = typed(tree.thenp, pt.dropIfProto)(using cond1.nullableContextIf(true)) - val elsep0 = typed(tree.elsep, pt.dropIfProto)(using cond1.nullableContextIf(false)) + val thenp0 = typed(tree.thenp, branchPt)(using cond1.nullableContextIf(true)) + val elsep0 = typed(tree.elsep, branchPt)(using cond1.nullableContextIf(false)) thenp0 :: elsep0 :: Nil } assignType(cpy.If(tree)(cond1, thenp1, elsep1), thenp1, elsep1) @@ -1596,14 +1603,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedMatchFinish(tree, sel1, selType, tree.cases, pt) } + /** Are some form of brackets necessary to annotate the tree `sel` as `@unchecked`? + * If so, return a Some(opening bracket, closing bracket), otherwise None. + */ + def uncheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match + case _: untpd.If + | _: untpd.Match + | _: untpd.ForYield + | _: untpd.ParsedTry + | _: untpd.Try => Some("(", ")") + case _: untpd.Block => Some("{", "}") + case _ => None + result match { case result @ Match(sel, CaseDef(pat, _, _) :: _) => tree.selector.removeAttachment(desugar.CheckIrrefutable) match { - case Some(checkMode) => + case Some(checkMode) if !sel.tpe.hasAnnotation(defn.UncheckedAnnot) => val isPatDef = checkMode == desugar.MatchCheck.IrrefutablePatDef - if (!checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `future-migration`) - if (isPatDef) patch(Span(tree.selector.span.end), ": @unchecked") - else patch(Span(pat.span.start), "case ") + if !checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `future-migration` then + if isPatDef then uncheckedBrackets(tree.selector) match + case None => + patch(Span(tree.selector.span.end), ": @unchecked") + case Some(bl, br) => + patch(Span(tree.selector.span.start), s"$bl") + patch(Span(tree.selector.span.end), s"$br: @unchecked") + else + patch(Span(tree.span.start), "case ") // skip exhaustivity check in later phase // TODO: move the check above to patternMatcher phase @@ -2356,25 +2381,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** If `ref` is an implicitly parameterized trait, pass an implicit argument list. * Otherwise, if `ref` is a parameterized trait, error. - * Note: Traits and classes currently always have at least an empty parameter list () - * before the implicit parameters (this is inserted if not given in source). - * We skip this parameter list when deciding whether a trait is parameterless or not. + * Note: Traits and classes have sometimes a synthesized empty parameter list () + * in front or after the implicit parameter(s). See NamerOps.normalizeIfConstructor. + * We synthesize a () argument at the correct place in this case. * @param ref The tree referring to the (parent) trait * @param psym Its type symbol - * @param cinfo The info of its constructor */ - def maybeCall(ref: Tree, psym: Symbol): Tree = psym.primaryConstructor.info.stripPoly match - case cinfo @ MethodType(Nil) if cinfo.resultType.isImplicitMethod => + def maybeCall(ref: Tree, psym: Symbol): Tree = + def appliedRef = typedExpr(untpd.New(untpd.TypedSplice(ref)(using superCtx), Nil))(using superCtx) - case cinfo @ MethodType(Nil) if !cinfo.resultType.isInstanceOf[MethodType] => - ref - case cinfo: MethodType => - if !ctx.erasedTypes then // after constructors arguments are passed in super call. - typr.println(i"constr type: $cinfo") - report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos) - ref - case _ => - ref + def dropContextual(tp: Type): Type = tp.stripPoly match + case mt: MethodType if mt.isContextualMethod => dropContextual(mt.resType) + case _ => tp + psym.primaryConstructor.info.stripPoly match + case cinfo @ MethodType(Nil) + if cinfo.resultType.isImplicitMethod && !cinfo.resultType.isContextualMethod => + appliedRef + case cinfo => + val cinfo1 = dropContextual(cinfo) + cinfo1 match + case cinfo1 @ MethodType(Nil) if !cinfo1.resultType.isInstanceOf[MethodType] => + if cinfo1 ne cinfo then appliedRef else ref + case cinfo1: MethodType if !ctx.erasedTypes => + report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos) + ref + case _ => + ref val seenParents = mutable.Set[Symbol]() @@ -2602,7 +2634,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted() var stats1 = typedStats(tree.stats, pkg.moduleClass)._1 if (!ctx.isAfterTyper) - stats1 = stats1 ++ typedBlockStats(MainProxies.mainProxies(stats1))._1 + stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1 cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef) } case _ => @@ -2876,6 +2908,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.Splice => typedSplice(tree, pt) case tree: untpd.TypSplice => typedTypSplice(tree, pt) case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here + case tree: untpd.Hole => typedHole(tree, pt) case _ => typedUnadapted(desugar(tree), pt, locked) } @@ -3403,7 +3436,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def isContextBoundParams = wtp.stripPoly match case MethodType(EvidenceParamName(_) :: _) => true case _ => false - if sourceVersion == `future-migration` && isContextBoundParams + if sourceVersion == `future-migration` && isContextBoundParams && pt.args.nonEmpty then // Under future-migration, don't infer implicit arguments yet for parameters // coming from context bounds. Issue a warning instead and offer a patch. report.migrationWarning( @@ -3674,14 +3707,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree.symbol != defn.StringContext_f && tree.symbol != defn.StringContext_s) if (ctx.settings.XignoreScala2Macros.value) { - report.warning("Scala 2 macro cannot be used in Dotty, this call will crash at runtime. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html", tree.srcPos.startPos) + report.warning("Scala 2 macro cannot be used in Dotty, this call will crash at runtime. See https://docs.scala-lang.org/scala3/reference/dropped-features/macros.html", tree.srcPos.startPos) Throw(New(defn.MatchErrorClass.typeRef, Literal(Constant(s"Reached unexpanded Scala 2 macro call to ${tree.symbol.showFullName} compiled with -Xignore-scala2-macros.")) :: Nil)) .withType(tree.tpe) .withSpan(tree.span) } else { report.error( - """Scala 2 macro cannot be used in Dotty. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html + """Scala 2 macro cannot be used in Dotty. See https://docs.scala-lang.org/scala3/reference/dropped-features/macros.html |To turn this error into a warning, pass -Xignore-scala2-macros to the compiler""".stripMargin, tree.srcPos.startPos) tree } diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 88212c3bb96c..ca114fe84ccf 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -171,7 +171,7 @@ class VarianceChecker(using Context) { val towner = tvar.owner if towner.isAllOf(EnumCase) && towner.isClass && tvar.is(Synthetic) then val example = - "See an example at http://dotty.epfl.ch/docs/reference/enums/adts.html#parameter-variance-of-enums" + "See an example at https://docs.scala-lang.org/scala3/reference/enums/adts.html#parameter-variance-of-enums" i"\n${hl("enum case")} ${towner.name} requires explicit declaration of $tvar to resolve this issue.\n$example" else "" diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 06a064860ac4..654fa94693c6 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -245,11 +245,12 @@ object QuoteMatcher { ref match case Select(qual1, _) => qual1 =?= qual2 case ref: Ident => - ref.tpe match - case TermRef(qual: TermRef, _) => tpd.ref(qual) =?= qual2 - case TermRef(qual: ThisType, _) if qual.classSymbol.is(Module, butNot = Package) => - tpd.ref(qual.classSymbol.companionModule) =?= qual2 - case _ => matched + if qual2.existsSubTree(_.symbol == defn.QuotedRuntimePatterns_patternHole) then + // Prefix has a hole, so we need to match the prefix to extract the value of the hole + tpd.desugarIdentPrefix(ref) =?= qual2 + else + matched + /* Match reference */ case _: Ident if symbolMatch(scrutinee, pattern) => matched /* Match type */ diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7641b9f0489b..da560ab6e240 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1893,6 +1893,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end AppliedTypeTypeTest object AppliedType extends AppliedTypeModule: + def apply(tycon: TypeRepr, args: List[TypeRepr]): AppliedType = + Types.AppliedType(tycon, args) def unapply(x: AppliedType): (TypeRepr, List[TypeRepr]) = (AppliedTypeMethods.tycon(x), AppliedTypeMethods.args(x)) end AppliedType @@ -3035,11 +3037,19 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end reflect def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?]): scala.quoted.Expr[T] = - val tree = PickledQuotes.unpickleTerm(pickled, typeHole, termHole) + val tree = PickledQuotes.unpickleTerm(pickled, PickledQuotes.TypeHole.V1(typeHole), PickledQuotes.ExprHole.V1(termHole)) + new ExprImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Expr[T]] + + def unpickleExprV2[T](pickled: String | List[String], types: Seq[Type[?]], termHole: Null | ((Int, Seq[Type[?] | Expr[Any]], Quotes) => Expr[?])): scala.quoted.Expr[T] = + val tree = PickledQuotes.unpickleTerm(pickled, PickledQuotes.TypeHole.V2(types), PickledQuotes.ExprHole.V2(termHole)) new ExprImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Expr[T]] def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?]): scala.quoted.Type[T] = - val tree = PickledQuotes.unpickleTypeTree(pickled, typeHole, termHole) + val tree = PickledQuotes.unpickleTypeTree(pickled, PickledQuotes.TypeHole.V1(typeHole)) + new TypeImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Type[T]] + + def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Seq[Type[?]]): scala.quoted.Type[T] = + val tree = PickledQuotes.unpickleTypeTree(pickled, PickledQuotes.TypeHole.V2(types)) new TypeImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Type[T]] object ExprMatch extends ExprMatchModule: diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6b2af4c1ff85..5681bd480511 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -73,6 +73,8 @@ class CompilationTests { aggregateTests( compileFile("tests/rewrites/rewrites.scala", scala2CompatMode.and("-rewrite", "-indent")), compileFile("tests/rewrites/rewrites3x.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), + compileFile("tests/rewrites/filtering-fors.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), + compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/i8982.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i9632.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i11895.scala", defaultOptions.and("-indent", "-rewrite")), @@ -249,6 +251,7 @@ class CompilationTests { compileFilesInDir("tests/explicit-nulls/pos-patmat", explicitNullsOptions and "-Xfatal-warnings"), compileFilesInDir("tests/explicit-nulls/unsafe-common", explicitNullsOptions and "-language:unsafeNulls"), compileFile("tests/explicit-nulls/pos-special/i14682.scala", explicitNullsOptions and "-Ysafe-init"), + compileFile("tests/explicit-nulls/pos-special/i14947.scala", explicitNullsOptions and "-Ytest-pickler" and "-Xprint-types"), ) }.checkCompile() diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala new file mode 100644 index 000000000000..9edc3f5b1cf9 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -0,0 +1,94 @@ +package dotty.tools.dotc.coverage + +import org.junit.Test +import org.junit.AfterClass +import org.junit.Assert.* +import org.junit.experimental.categories.Category + +import dotty.{BootstrappedOnlyTests, Properties} +import dotty.tools.vulpix.* +import dotty.tools.vulpix.TestConfiguration.* +import dotty.tools.dotc.Main + +import java.nio.file.{Files, FileSystems, Path, Paths, StandardCopyOption} +import scala.jdk.CollectionConverters.* +import scala.util.Properties.userDir +import scala.language.unsafeNulls +import scala.collection.mutable.Buffer + +@Category(Array(classOf[BootstrappedOnlyTests])) +class CoverageTests: + import CoverageTests.{*, given} + + private val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") + private val rootSrc = Paths.get(userDir, "tests", "coverage") + + @Test + def checkCoverageStatements(): Unit = + checkCoverageIn(rootSrc.resolve("pos"), false) + + @Test + def checkInstrumentedRuns(): Unit = + checkCoverageIn(rootSrc.resolve("run"), true) + + def checkCoverageIn(dir: Path, run: Boolean)(using TestGroup): Unit = + /** Converts \ to / on windows, to make the tests pass without changing the serialization. */ + def fixWindowsPaths(lines: Buffer[String]): Buffer[String] = + val separator = java.io.File.separatorChar + if separator != '/' then + lines.map(_.replace(separator, '/')) + else + lines + end fixWindowsPaths + + def runOnFile(p: Path): Boolean = + scalaFile.matches(p) && + (Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains)) + + Files.walk(dir).filter(runOnFile).forEach(path => { + val fileName = path.getFileName.toString.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(path, dir, run) + val targetFile = targetDir.resolve(s"scoverage.coverage") + val expectFile = path.resolveSibling(s"$fileName.scoverage.check") + if updateCheckFiles then + Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) + else + val expected = fixWindowsPaths(Files.readAllLines(expectFile).asScala) + val obtained = fixWindowsPaths(Files.readAllLines(targetFile).asScala) + if expected != obtained then + for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do + Console.err.println(s"wrong line ${i+1}:") + Console.err.println(s" expected: $exp") + Console.err.println(s" actual : $actual") + fail(s"$targetFile differs from expected $expectFile") + + }) + + /** Generates the coverage report for the given input file, in a temporary directory. */ + def computeCoverageInTmp(inputFile: Path, sourceRoot: Path, run: Boolean)(using TestGroup): Path = + val target = Files.createTempDirectory("coverage") + val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-sourceroot", sourceRoot.toString) + val test = compileFile(inputFile.toString, options) + if run then + test.checkRuns() + else + test.checkCompile() + target + +object CoverageTests extends ParallelTesting: + import scala.concurrent.duration.* + + def maxDuration = 30.seconds + def numberOfSlaves = 1 + + def safeMode = Properties.testsSafeMode + def testFilter = Properties.testsFilter + def isInteractive = SummaryReport.isInteractive + def updateCheckFiles = Properties.testsUpdateCheckfile + + given summaryReport: SummaryReporting = SummaryReport() + @AfterClass def tearDown(): Unit = + super.cleanup() + summaryReport.echoSummary() + + given TestGroup = TestGroup("instrumentCoverage") diff --git a/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala b/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala index e6b17fade86f..23054cdf0dfb 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala @@ -1,9 +1,11 @@ -package dotty.tools.dotc.printing +package dotty.tools +package dotc +package printing -import dotty.tools.DottyTest -import dotty.tools.dotc.ast.{Trees,tpd} -import dotty.tools.dotc.core.Names._ -import dotty.tools.dotc.core.Symbols._ +import ast.{ Trees, tpd } +import core.Names._ +import core.Symbols._ +import core.Decorators._ import dotty.tools.dotc.core.Contexts.Context import org.junit.Assert.assertEquals @@ -49,4 +51,26 @@ class PrinterTests extends DottyTest { assertEquals("Int & (Boolean | String)", bar.tpt.show) } } + + @Test def string: Unit = assertEquals("foo", i"${"foo"}") + + import core.Flags._ + @Test def flagsSingle: Unit = assertEquals("final", i"$Final") + @Test def flagsSeq: Unit = assertEquals(", final", i"${Seq(JavaStatic, Final)}%, %") + @Test def flagsTuple: Unit = assertEquals("(,final)", i"${(JavaStatic, Final)}") + @Test def flagsSeqOfTuple: Unit = assertEquals("(final,given), (private,lazy)", i"${Seq((Final, Given), (Private, Lazy))}%, %") + + class StorePrinter extends config.Printers.Printer: + var string: String = "" + override def println(msg: => String) = string = msg + + @Test def testShowing: Unit = + val store = StorePrinter() + (JavaStatic | Final).showing(i"flags=$result", store) + assertEquals("flags=final ", store.string) + + @Test def TestShowingWithOriginalType: Unit = + val store = StorePrinter() + (JavaStatic | Final).showing(i"flags=${if result.is(Private) then result &~ Private else result | Private}", store) + assertEquals("flags=private final ", store.string) } diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 6df8c734ad3e..5a57ede4ceb5 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -666,7 +666,7 @@ trait ParallelTesting extends RunnerOrchestration { self => if (didFail) { reportFailed() failedTestSources.toSet.foreach(addFailedTest) - reproduceInstructions.iterator.foreach(addReproduceInstruction) + reproduceInstructions.foreach(addReproduceInstruction) } else reportPassed() } @@ -980,7 +980,7 @@ trait ParallelTesting extends RunnerOrchestration { self => cleanup() if (!shouldFail && test.didFail) { - fail(s"Expected no errors when compiling, failed for the following reason(s):\n${ reasonsForFailure(test) }") + fail(s"Expected no errors when compiling, failed for the following reason(s):\n${reasonsForFailure(test)}\n") } else if (shouldFail && !test.didFail) { fail("Pos test should have failed, but didn't") @@ -1064,7 +1064,13 @@ trait ParallelTesting extends RunnerOrchestration { self => target.copy(dir = copyToDir(outDir, dir)) } - new RewriteTest(copiedTargets, checkFileMap, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + val test = new RewriteTest(copiedTargets, checkFileMap, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + + cleanup() + + if test.didFail then + fail("Rewrite test failed") + this } @@ -1077,14 +1083,16 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Extract `Failure` set and render from `Test` */ private def reasonsForFailure(test: Test): String = { val failureReport = - if (test.failureCount == 0) "" - else s"\n - encountered ${test.failureCount} test failures(s)" + if test.failureCount == 0 then "" + else s"encountered ${test.failureCount} test failure(s):\n" failureReport + test.failureReasons.collect { case test.TimeoutFailure(title) => s" - test '$title' timed out" case test.JavaCompilationFailure(msg) => s" - java compilation failed with:\n${ msg.linesIterator.map(" " + _).mkString("\n") }" + case test.Generic => + " - generic failure (see test output)" }.mkString("\n") } diff --git a/docs/_docs/internals/gadts.md b/docs/_docs/internals/gadts.md new file mode 100644 index 000000000000..777b9dd32e39 --- /dev/null +++ b/docs/_docs/internals/gadts.md @@ -0,0 +1,205 @@ +# GADTs - Broad overview + +There are multiple levels to the implementation. They deal with slightly different problems. The most important levels are the following ones: + +1. Figuring out which relationships are necessary (PatternTypeConstrainer) +2. Breaking down the relationships (TypeComparer) +3. Recording and bookkeeping of relationships (GadtConstraint) +4. Looking up the information (TypeComparer, GadtConstraint) + +Out of the levels above, 4. is by far the simplest one. + +There are also other parts to supporting GADTs. Roughly in order of importance, they are: + +1. At specific points, abstract types are added to GadtConstraint. For instance, when entering a method, we add all type parameters to GadtConstraint. +2. Casts need to be inserted when GADTs were used for type comparison. + 1. `TypeComparer` keeps track of whether a GADT constraint was used in a mutable variable `usedGadt`. +3. GADT constraints need to be stored in attachments to be used in PostTyper. + 1. Attachment key is named `inferredGadtConstraints`. +4. When we select members on a type that may have GADT constraints, we perform special "healing" by approximating the type using those constraints. We cannot take the constraints into account because member lookup is cached, and GADT constraints are only valid for specific scopes. + +# Useful widgets + +## Expr + +This is the classical GADT example: + +```scala +enum Expr[T] { + case IntLit(value: Int) extends Expr[Int] + case BoolLit(value: Boolean) extends Expr[Boolean] + case IfExpr( + cond: Expr[Boolean], + when: Expr[T], + otherwise: Expr[T], + ) +} +``` + +## EQ + +The following enum will result in an equality constraint between `S` and `T` if we match on it: + +```scala +enum EQ[S, T] { + case Refl[U]() extends EQ[U, U] +} +``` + +## SUB + +The following enum will result in a subtyping constraint `S <: T` if we match on it: + +```scala +enum SUB[-S, +T] { + case Refl[U]() extends SUB[U, U] +} +``` + +# Details of above + +## What abstract types can have GADT constraints + +Right now, we record GADT constraints for: + +- function/method type parameters +- class type parameters + +There is a branch on the way which will also record them for type members (so path-dependent types) and singleton types. It has a paper associated: "Implementing path-depepdent GADTs for Scala 3". + +## What are necessary relationships? Any examples? + +### Covariance means no constraint is necessary + +Standard (non-case) classes allow "strange" inheritance which means that we cannot infer any information from covariant type parameters. + +```scala +class Expr[+A] +class IntList extends Expr[List[Int]] + +def foo[T](e: Expr[List[T]]): T = + e match { + case _ : IntList => + // e : Expr[List[Int]] + // T <: Int + 0 + } + +class Weird(list: List[String]) extends IntList with Expr[Nothing] +``` + +Case classes have a special check which disallows inheritance like `Weird`. This means we can infer extra information from them. + +## Breaking down the constraints + +```scala +class Expr[A] +class IntList extends Expr[List[Int]] + +def foo[T](e: Expr[List[T]]): T = + e match { + case _ : IntList => + // Level 1: + // We start with e : Expr[List[T]] + // We check that e : IntList <: Expr[List[Int] + // Expr is invariant, + // so we have List[Int] <: List[T] , List[T] <: List[Int] + // Level 2: + // We compare List[Int] <: List[T] + // We record Int <: T + // We compare List[T] <: List[Int] + // We record T <: Int + 0 + } +``` + +## Relation betweeen GadtConstraint and OrderingConstraint + +### Internal and external types + +GadtConstraint uses OrderingConstraint as the datastructure to record information about GADT constraints. + +OrderingConstraint only supports working with TypeParamRefs. + +GadtConstraint wants to record information about things other than TypeParamRefs. + +To solve this, GadtConstraint internally creates TypeParamRefs which it adds to the OrderingConstraint it keeps internally. Whenever a GADT constraint is added, we "internalize" the type by replacing all the external types with the internal TypeParamRefs. Whenever we take bound information out of the GADT constraint, we need to "externalize" the types by replacing the internal TypeParamRefs with their external versions. To implement this, GadtConstraint keeps a bidirectional mapping between the external types and the internal TypeParamRefs. + +The TypeParamRefs and TypeVars registered in one constraint cannot ever be present in types mentioned in the other type constraint. The internal TypeParamRefs and TypeVars cannot ever leak out of the GadtConstraint. We cannot ever record a bound in GadtConstraint which mentions TypeParamRefs used for type inference. (That part is ensured by the way TypeComparer is organised – we will always try to record bounds in the "normal" constraint before recording a GADT bound.) + +# Other details + +## TypeComparer approximations + +TypeComparer sometimes approximates the types it compares. Let's see an example based on these definitions: + +```scala +class Expr[T] +class IntList extends Expr[Int] +``` + +when comparing if `IntList <: Expr[Int]`, `TypeComparer` will approximate `IntList` to `Expr[Int]`. Then it will compare `Expr[Int] <: Expr[Int]` with appropriate variables set. + +The variables which TypeComparer sets are `approxState` and `frozenGadt`. + +## Necessary/sufficient either + +TypeComparer sometimes needs to approximate some constraints, specifically when dealing with intersection and union types. The way this approximation works changes if we're currently inferring GADT constraints. This is hopefully documented well in TypeComparer in doc comments for `necessaryEither` and `sufficientEither`. + +## Types bound in patterns + +```scala +(list : List[Int]) match { + case lst : List[a] => + // a is a new type bound in the pattern. + // We do not record any information about a. + // We should know that a <: Int. + // (Or it's fine to just have a =:= Int.) + // We would not have this issue if we used a custom unapply. + // Type case patterns create a fresh symbol even if they shouldn't. + // (See indexPattern in Typer.) +} +``` + +## Internal structure of OrderingConstraint + +Imagine we have two type parameters in scope, `A` and `B`. + +We could record the following in `ctx.gadt`: + +```text +A <: Int +B <: A +``` + +Then, we expect that calling ``ctx.gadt.bounds(`B`)`` will return `` `<: Int` ``. + +In order to handle this, `GadtConstraint` relies on `OrderingConstraint`. Internally, it will represent the above constraints as follows: + +```text +A <: Int +B <: Int +B <: A +``` + +The first two constraints are "entries" – they are easy to look up whenever we ask for bounds of `A` or `B`. The third constraint is an ordering – it helps with correctly propagating the bounds we record. + +# Possible broad improvements + +## Allow OrderingConstraint to record bounds for things other than TypeParamRefs + +This would mean we no longer need to keep the bidirectional mapping in GadtConstraint. + +## Not mixing OrderingConstraint and ConstraintHandling in GadtConstraint + +GadtConstraint right now mixes OrderingConstraint and ConstraintHandling. The first one is supposed to be the immutable constraint datastructure. The second one implements mutable functionality around a variable containing the immutable datastructure. + +GadtConstraint mixes them both. Things would be better organised if GadtConstraint was split like the normal constraint. + +## Creating a separate TypeComparer for breaking down types into GADT constraints + +TypeComparer is biased towards one specific way of approximating constraints. When we infer types, it's ok to be "optimistic". When inferring GADT constraints, we should be as pessimistic as possible, in order to only infer constraints which are necessary. + +TypeComparer was originally written to support type inference and GADTs were only added later on. This means that the "default" way TypeComparer approximates wasn't even noticeable before, but when inferring GADT constraints could result in inferring unsound information. + +We could potentially fix this by creating a special TypeComparer which would *only* "break down" subtype relationships to record GADT constraints. diff --git a/docs/_docs/reference/changed-features/imports.md b/docs/_docs/reference/changed-features/imports.md index df9657115822..7d364ce68b92 100644 --- a/docs/_docs/reference/changed-features/imports.md +++ b/docs/_docs/reference/changed-features/imports.md @@ -46,14 +46,14 @@ are offered under settings `-source 3.1-migration -rewrite`. ### Syntax -```ebnf -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ; -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ; +``` +Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} +ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*' | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*' | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} ``` diff --git a/docs/_docs/reference/changed-features/match-syntax.md b/docs/_docs/reference/changed-features/match-syntax.md index bd39fc39b033..cff77262611b 100644 --- a/docs/_docs/reference/changed-features/match-syntax.md +++ b/docs/_docs/reference/changed-features/match-syntax.md @@ -47,10 +47,10 @@ The syntactical precedence of match expressions has been changed. The new syntax of match expressions is as follows. -```ebnf +``` InfixExpr ::= ... - | InfixExpr MatchClause ; + | InfixExpr MatchClause SimpleExpr ::= ... - | SimpleExpr ‘.’ MatchClause ; -MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ ; + | SimpleExpr ‘.’ MatchClause +MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ ``` diff --git a/docs/_docs/reference/changed-features/pattern-bindings.md b/docs/_docs/reference/changed-features/pattern-bindings.md index 7d84f97cc4c3..2c8d1c10ceae 100644 --- a/docs/_docs/reference/changed-features/pattern-bindings.md +++ b/docs/_docs/reference/changed-features/pattern-bindings.md @@ -50,8 +50,8 @@ for case (x, y) <- elems yield (y, x) // returns List((2, 1), (4, 3)) ## Syntax Changes Generators in for expressions may be prefixed with `case`. -```ebnf -Generator ::= [‘case’] Pattern1 ‘<-’ Expr ; +``` +Generator ::= [‘case’] Pattern1 ‘<-’ Expr ``` ## Migration diff --git a/docs/_docs/reference/changed-features/structural-types-spec.md b/docs/_docs/reference/changed-features/structural-types-spec.md index 63cf258888fa..290189a67eda 100644 --- a/docs/_docs/reference/changed-features/structural-types-spec.md +++ b/docs/_docs/reference/changed-features/structural-types-spec.md @@ -6,11 +6,11 @@ movedTo: https://docs.scala-lang.org/scala3/reference/changed-features/structura ## Syntax -```ebnf -SimpleType ::= ... | Refinement ; -Refinement ::= ‘{’ RefineStatSeq ‘}’ ; -RefineStatSeq ::= RefineStat {semi RefineStat} ; -RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl ; +``` +SimpleType ::= ... | Refinement +Refinement ::= ‘{’ RefineStatSeq ‘}’ +RefineStatSeq ::= RefineStat {semi RefineStat} +RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl ``` ## Implementation of Structural Types diff --git a/docs/_docs/reference/changed-features/vararg-splices.md b/docs/_docs/reference/changed-features/vararg-splices.md index d74962cec3ff..1d8b61408176 100644 --- a/docs/_docs/reference/changed-features/vararg-splices.md +++ b/docs/_docs/reference/changed-features/vararg-splices.md @@ -24,12 +24,12 @@ The old syntax for splice arguments will be phased out. ## Syntax -```ebnf +``` ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ - | ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’ ; + | ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’ ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ - | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ ; + | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ ``` ## Compatibility considerations diff --git a/docs/_docs/reference/contextual/context-bounds.md b/docs/_docs/reference/contextual/context-bounds.md index 756f0c3a36bf..e336f00cc463 100644 --- a/docs/_docs/reference/contextual/context-bounds.md +++ b/docs/_docs/reference/contextual/context-bounds.md @@ -40,7 +40,7 @@ done automatically under `-rewrite`. ## Syntax -```ebnf -TypeParamBounds ::= [SubtypeBounds] {ContextBound} ; -ContextBound ::= ‘:’ Type ; +``` +TypeParamBounds ::= [SubtypeBounds] {ContextBound} +ContextBound ::= ‘:’ Type ``` diff --git a/docs/_docs/reference/contextual/context-functions-spec.md b/docs/_docs/reference/contextual/context-functions-spec.md index 1a5b43691b63..3c61e0e0c6ca 100644 --- a/docs/_docs/reference/contextual/context-functions-spec.md +++ b/docs/_docs/reference/contextual/context-functions-spec.md @@ -6,11 +6,11 @@ movedTo: https://docs.scala-lang.org/scala3/reference/contextual/context-functio ## Syntax -```ebnf +``` Type ::= ... - | FunArgTypes ‘?=>’ Type ; + | FunArgTypes ‘?=>’ Type Expr ::= ... - | FunParams ‘?=>’ Expr ; + | FunParams ‘?=>’ Expr ``` Context function types associate to the right, e.g. diff --git a/docs/_docs/reference/contextual/derivation.md b/docs/_docs/reference/contextual/derivation.md index df1bcddcf80c..972ac945a22d 100644 --- a/docs/_docs/reference/contextual/derivation.md +++ b/docs/_docs/reference/contextual/derivation.md @@ -354,12 +354,12 @@ hand side of this definition in the same way as an instance defined in ADT compa ### Syntax -```ebnf -Template ::= InheritClauses [TemplateBody] ; -EnumDef ::= id ClassConstr InheritClauses EnumBody ; -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ; +``` +Template ::= InheritClauses [TemplateBody] +EnumDef ::= id ClassConstr InheritClauses EnumBody +InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ConstrApps ::= ConstrApp {‘with’ ConstrApp} - | ConstrApp {‘,’ ConstrApp} ; + | ConstrApp {‘,’ ConstrApp} ``` **Note:** To align `extends` clauses and `derives` clauses, Scala 3 also allows multiple diff --git a/docs/_docs/reference/contextual/extension-methods.md b/docs/_docs/reference/contextual/extension-methods.md index 3d2dc4109ca5..c2a12081cf99 100644 --- a/docs/_docs/reference/contextual/extension-methods.md +++ b/docs/_docs/reference/contextual/extension-methods.md @@ -285,20 +285,20 @@ def position(s: String)(ch: Char, n: Int): Int = Here are the syntax changes for extension methods and collective extensions relative to the [current syntax](../syntax.md). -```ebnf -BlockStat ::= ... | Extension ; -TemplateStat ::= ... | Extension ; -TopStat ::= ... | Extension ; +``` +BlockStat ::= ... | Extension +TemplateStat ::= ... | Extension +TopStat ::= ... | Extension Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods ; -ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> ; -ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef ; + ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods +ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> +ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef ``` In the above the notation `<<< ts >>>` in the production rule `ExtMethods` is defined as follows : -```ebnf -<<< ts >>> ::= ‘{’ ts ‘}’ | indent ts outdent ; +``` +<<< ts >>> ::= ‘{’ ts ‘}’ | indent ts outdent ``` `extension` is a soft keyword. It is recognized as a keyword only if it appears diff --git a/docs/_docs/reference/contextual/given-imports.md b/docs/_docs/reference/contextual/given-imports.md index cd49eda49d1b..3e3bc3d275cc 100644 --- a/docs/_docs/reference/contextual/given-imports.md +++ b/docs/_docs/reference/contextual/given-imports.md @@ -103,15 +103,15 @@ given instances once their user base has migrated. ### Syntax -```ebnf -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ; -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ; -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ; +``` +Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} +Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} +ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*' | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*' | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} ``` diff --git a/docs/_docs/reference/contextual/givens.md b/docs/_docs/reference/contextual/givens.md index 9c440d56ca1e..1d0baae9e257 100644 --- a/docs/_docs/reference/contextual/givens.md +++ b/docs/_docs/reference/contextual/givens.md @@ -173,14 +173,14 @@ is created for each reference. Here is the syntax for given instances: -```ebnf +``` TmplDef ::= ... - | ‘given’ GivenDef ; + | ‘given’ GivenDef GivenDef ::= [GivenSig] StructuralInstance | [GivenSig] AnnotType ‘=’ Expr - | [GivenSig] AnnotType ; -GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ ; -StructuralInstance ::= ConstrApp {‘with’ ConstrApp} ‘with’ TemplateBody ; + | [GivenSig] AnnotType +GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ +StructuralInstance ::= ConstrApp {‘with’ ConstrApp} ‘with’ TemplateBody ``` A given instance starts with the reserved word `given` and an optional _signature_. The signature diff --git a/docs/_docs/reference/contextual/using-clauses.md b/docs/_docs/reference/contextual/using-clauses.md index 31caf9dbd1d1..8c522d82c402 100644 --- a/docs/_docs/reference/contextual/using-clauses.md +++ b/docs/_docs/reference/contextual/using-clauses.md @@ -144,10 +144,10 @@ def summon[T](using x: T): x.type = x Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](../syntax.md). `using` is a soft keyword, recognized only at the start of a parameter or argument list. It can be used as a normal identifier everywhere else. -```ebnf -ClsParamClause ::= ... | UsingClsParamClause ; -DefParamClauses ::= ... | UsingParamClause ; -UsingClsParamClause ::= ‘(’ ‘using’ (ClsParams | Types) ‘)’ ; -UsingParamClause ::= ‘(’ ‘using’ (DefParams | Types) ‘)’ ; -ParArgumentExprs ::= ... | ‘(’ ‘using’ ExprsInParens ‘)’ ; +``` +ClsParamClause ::= ... | UsingClsParamClause +DefParamClauses ::= ... | UsingParamClause +UsingClsParamClause ::= ‘(’ ‘using’ (ClsParams | Types) ‘)’ +UsingParamClause ::= ‘(’ ‘using’ (DefParams | Types) ‘)’ +ParArgumentExprs ::= ... | ‘(’ ‘using’ ExprsInParens ‘)’ ``` diff --git a/docs/_docs/reference/enums/adts.md b/docs/_docs/reference/enums/adts.md index fa2aa2ebd113..6fc5be78d7b8 100644 --- a/docs/_docs/reference/enums/adts.md +++ b/docs/_docs/reference/enums/adts.md @@ -154,18 +154,18 @@ The changes are specified below as deltas with respect to the Scala syntax given 1. Enum definitions are defined as follows: - ```ebnf - TmplDef ::= `enum' EnumDef ; - EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody ; - EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ ; + ``` + TmplDef ::= `enum' EnumDef + EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody + EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ EnumStat ::= TemplateStat - | {Annotation [nl]} {Modifier} EnumCase ; + | {Annotation [nl]} {Modifier} EnumCase ``` 2. Cases of enums are defined as follows: - ```ebnf - EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) ; + ``` + EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) ``` ### Reference diff --git a/docs/_docs/reference/experimental/main-annotation.md b/docs/_docs/reference/experimental/main-annotation.md new file mode 100644 index 000000000000..d2172d97a284 --- /dev/null +++ b/docs/_docs/reference/experimental/main-annotation.md @@ -0,0 +1,97 @@ +--- +layout: doc-page +title: "MainAnnotation" +--- + +`MainAnnotation` provides a generic way to define main annotations such as `@main`. + +When a users annotates a method with an annotation that extends `MainAnnotation` a class with a `main` method will be generated. The main method will contain the code needed to parse the command line arguments and run the application. + +```scala +/** Sum all the numbers + * + * @param first Fist number to sum + * @param rest The rest of the numbers to sum + */ +@myMain def sum(first: Int, second: Int = 0, rest: Int*): Int = first + second + rest.sum +``` + +```scala +object foo { + def main(args: Array[String]): Unit = { + val mainAnnot = new myMain() + val info = new Info( + name = "foo.main", + documentation = "Sum all the numbers", + parameters = Seq( + new Parameter("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum", Seq()), + new Parameter("second", "scala.Int", hasDefault=true, isVarargs=false, "", Seq()), + new Parameter("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum", Seq()) + ) + ) + val mainArgsOpt = mainAnnot.command(info, args) + if mainArgsOpt.isDefined then + val mainArgs = mainArgsOpt.get + val args0 = mainAnnot.argGetter[Int](info.parameters(0), mainArgs(0), None) // using a parser of Int + val args1 = mainAnnot.argGetter[Int](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) // using a parser of Int + val args2 = mainAnnot.varargGetter[Int](info.parameters(2), mainArgs.drop(2)) // using a parser of Int + mainAnnot.run(() => sum(args0(), args1(), args2()*)) + } +} +``` + +The implementation of the `main` method first instantiates the annotation and then call `command`. +When calling the `command`, the arguments can be checked and preprocessed. +Then it defines a series of argument getters calling `argGetter` for each parameter and `varargGetter` for the last one if it is a varargs. `argGetter` gets an optional lambda that computes the default argument. +Finally, the `run` method is called to run the application. It receives a by-name argument that contains the call the annotated method with the instantiations arguments (using the lambdas from `argGetter`/`varargGetter`). + + +Example of implementation of `myMain` that takes all arguments positionally. It used `util.CommandLineParser.FromString` and expects no default arguments. For simplicity, any errors in preprocessing or parsing results in crash. + +```scala +// Parser used to parse command line arguments +import scala.util.CommandLineParser.FromString[T] + +// Result type of the annotated method is Int and arguments are parsed using FromString +@experimental class myMain extends MainAnnotation[FromString, Int]: + import MainAnnotation.{ Info, Parameter } + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = + if args.contains("--help") then + println(info.documentation) + None // do not parse or run the program + else if info.parameters.exists(_.hasDefault) then + println("Default arguments are not supported") + None + else if info.hasVarargs then + val numPlainArgs = info.parameters.length - 1 + if numPlainArgs <= args.length then + println("Not enough arguments") + None + else + Some(args) + else + if info.parameters.length <= args.length then + println("Not enough arguments") + None + else if info.parameters.length >= args.length then + println("Too many arguments") + None + else + Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = + () => parser.fromString(arg) + + def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] = + () => args.map(arg => parser.fromString(arg)) + + def run(program: () => Int): Unit = + println("executing program") + + try { + val result = program() + println("result: " + result) + println("executed program") +end myMain +``` diff --git a/docs/_docs/reference/experimental/named-typeargs-spec.md b/docs/_docs/reference/experimental/named-typeargs-spec.md index 9ec9fd200ed2..7bf5c2f43ccf 100644 --- a/docs/_docs/reference/experimental/named-typeargs-spec.md +++ b/docs/_docs/reference/experimental/named-typeargs-spec.md @@ -10,11 +10,11 @@ In this section we give more details about the [named type arguments](named-type The addition to the grammar is: -```ebnf +``` SimpleExpr1 ::= ... - | SimpleExpr (TypeArgs | NamedTypeArgs) ; -NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ ; -NamedTypeArg ::= id ‘=’ Type ; + | SimpleExpr (TypeArgs | NamedTypeArgs) +NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ +NamedTypeArg ::= id ‘=’ Type ``` Note in particular that named arguments cannot be passed to type constructors: diff --git a/docs/_docs/reference/metaprogramming/macros-spec.md b/docs/_docs/reference/metaprogramming/macros-spec.md index 91cd36173ede..d152377e6d62 100644 --- a/docs/_docs/reference/metaprogramming/macros-spec.md +++ b/docs/_docs/reference/metaprogramming/macros-spec.md @@ -10,13 +10,13 @@ movedTo: https://docs.scala-lang.org/scala3/reference/metaprogramming/macros-spe Compared to the [Scala 3 reference grammar](../syntax.md) there are the following syntax changes: -```ebnf +``` SimpleExpr ::= ... | ‘'’ ‘{’ Block ‘}’ | ‘'’ ‘[’ Type ‘]’ - | ‘$’ ‘{’ Block ‘}’ ; + | ‘$’ ‘{’ Block ‘}’ SimpleType ::= ... - | ‘$’ ‘{’ Block ‘}’ ; + | ‘$’ ‘{’ Block ‘}’ ``` In addition, an identifier `$x` starting with a `$` that appears inside a quoted expression or type is treated as a splice `${x}` and a quoted identifier @@ -57,36 +57,36 @@ extends simply-typed lambda calculus with quotes and splices. ### Syntax The syntax of terms, values, and types is given as follows: -```ebnf +``` Terms t ::= x variable (x: T) => t lambda t t application 't quote - $t splice ; + $t splice Values v ::= (x: T) => t lambda - 'u quote ; + 'u quote -Simple terms u ::= x | (x: T) => u | u u | 't ; +Simple terms u ::= x | (x: T) => u | u u | 't Types T ::= A base type T -> T function type - expr T quoted ; + expr T quoted ``` Typing rules are formulated using a stack of environments `Es`. Individual environments `E` consist as usual of variable bindings `x: T`. Environments can be combined using the two combinators `'` and `$`. -```ebnf +``` Environment E ::= () empty - E, x: T ; + E, x: T Env. stack Es ::= () empty E simple - Es * Es combined ; + Es * Es combined Separator * ::= ' - $ ; + $ ``` The two environment combinators are both associative with left and right identity `()`. @@ -108,9 +108,9 @@ rule says that splice and quotes cancel each other out. The third rule is a context rule; it says that reduction is allowed in the hole `[ ]` position of an evaluation context. Evaluation contexts `e` and splice evaluation context `e_s` are defined syntactically as follows: -```ebnf -Eval context e ::= [ ] | e t | v e | 'e_s[${e}] ; -Splice context e_s ::= [ ] | (x: T) => e_s | e_s t | u e_s ; +``` +Eval context e ::= [ ] | e t | v e | 'e_s[${e}] +Splice context e_s ::= [ ] | (x: T) => e_s | e_s t | u e_s ``` ### Typing rules diff --git a/docs/_docs/reference/metaprogramming/simple-smp.md b/docs/_docs/reference/metaprogramming/simple-smp.md index 89529bac1480..78277987c874 100644 --- a/docs/_docs/reference/metaprogramming/simple-smp.md +++ b/docs/_docs/reference/metaprogramming/simple-smp.md @@ -23,21 +23,21 @@ replace evaluation contexts with contextual typing rules. While this is more verbose, it makes it easier to set up the meta theory. ## Syntax -```ebnf +``` Terms t ::= x variable (x: T) => t lambda t t application ’t quote - ~t splice ; + ~t splice -Simple terms u ::= x | (x: T) => u | u u ; +Simple terms u ::= x | (x: T) => u | u u Values v ::= (x: T) => t lambda - ’u quoted value ; + ’u quoted value Types T ::= A base type T -> T function type - ’T quoted type ; + ’T quoted type ``` ## Operational semantics diff --git a/docs/_docs/reference/new-types/dependent-function-types-spec.md b/docs/_docs/reference/new-types/dependent-function-types-spec.md index 8fb0d03dc13c..c3915ba9b8ac 100644 --- a/docs/_docs/reference/new-types/dependent-function-types-spec.md +++ b/docs/_docs/reference/new-types/dependent-function-types-spec.md @@ -8,11 +8,11 @@ Initial implementation in [PR #3464](https://github.com/lampepfl/dotty/pull/3464 ## Syntax -```ebnf +``` FunArgTypes ::= InfixType | ‘(’ [ FunArgType {',' FunArgType } ] ‘)’ - | ‘(’ TypedFunParam {',' TypedFunParam } ‘)’ ; -TypedFunParam ::= id ‘:’ Type ; + | ‘(’ TypedFunParam {',' TypedFunParam } ‘)’ +TypedFunParam ::= id ‘:’ Type ``` Dependent function types associate to the right, e.g. diff --git a/docs/_docs/reference/new-types/intersection-types-spec.md b/docs/_docs/reference/new-types/intersection-types-spec.md index 6a6e579f4bae..8ed9305e1486 100644 --- a/docs/_docs/reference/new-types/intersection-types-spec.md +++ b/docs/_docs/reference/new-types/intersection-types-spec.md @@ -12,9 +12,9 @@ with the usual precedence and subject to usual resolving rules. Unless shadowed by another definition, it resolves to the type `scala.&`, which acts as a type alias to an internal representation of intersection types. -```ebnf -Type ::= ...| InfixType ; -InfixType ::= RefinedType {id [nl] RefinedType} ; +``` +Type ::= ...| InfixType +InfixType ::= RefinedType {id [nl] RefinedType} ``` ## Subtyping Rules diff --git a/docs/_docs/reference/new-types/type-lambdas-spec.md b/docs/_docs/reference/new-types/type-lambdas-spec.md index 5f52956a4d4e..5c791ba40272 100644 --- a/docs/_docs/reference/new-types/type-lambdas-spec.md +++ b/docs/_docs/reference/new-types/type-lambdas-spec.md @@ -6,11 +6,11 @@ movedTo: https://docs.scala-lang.org/scala3/reference/new-types/type-lambdas-spe ## Syntax -```ebnf -Type ::= ... | TypeParamClause ‘=>>’ Type ; -TypeParamClause ::= ‘[’ TypeParam {‘,’ TypeParam} ‘]’ ; -TypeParam ::= {Annotation} (id [HkTypeParamClause] | ‘_’) TypeBounds ; -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] ; +``` +Type ::= ... | TypeParamClause ‘=>>’ Type +TypeParamClause ::= ‘[’ TypeParam {‘,’ TypeParam} ‘]’ +TypeParam ::= {Annotation} (id [HkTypeParamClause] | ‘_’) TypeBounds +TypeBounds ::= [‘>:’ Type] [‘<:’ Type] ``` ### Type Checking diff --git a/docs/_docs/reference/other-new-features/export.md b/docs/_docs/reference/other-new-features/export.md index 89e054605bb7..85f03de4104e 100644 --- a/docs/_docs/reference/other-new-features/export.md +++ b/docs/_docs/reference/other-new-features/export.md @@ -134,20 +134,20 @@ more flexible way. ## Syntax changes: -```ebnf +``` TemplateStat ::= ... - | Export ; + | Export TopStat ::= ... - | Export ; -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ; -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ; + | Export +Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} +ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*’ | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*’ | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} ``` ## Elaboration of Export Clauses diff --git a/docs/_docs/reference/other-new-features/indentation-experimental.md b/docs/_docs/reference/other-new-features/indentation-experimental.md index 073c10e2f92c..cbdd41eeaf12 100644 --- a/docs/_docs/reference/other-new-features/indentation-experimental.md +++ b/docs/_docs/reference/other-new-features/indentation-experimental.md @@ -62,13 +62,13 @@ Braces can be omitted if the lambda starts with a parameter list and `=>` or `=> ## Syntax Changes -```ebnf +``` SimpleExpr ::= ... | SimpleExpr `:` IndentedArgument - | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument ; + | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument InfixExpr ::= ... - | InfixExpr id `:` IndentedArgument ; -IndentedArgument ::= indent (CaseClauses | Block) outdent ; + | InfixExpr id `:` IndentedArgument +IndentedArgument ::= indent (CaseClauses | Block) outdent ``` Note that a lambda argument must have the `=>` at the end of a line for braces diff --git a/docs/_docs/reference/other-new-features/indentation.md b/docs/_docs/reference/other-new-features/indentation.md index bc089d7eb0b4..46bc21e2597d 100644 --- a/docs/_docs/reference/other-new-features/indentation.md +++ b/docs/_docs/reference/other-new-features/indentation.md @@ -170,11 +170,11 @@ In each case, the `:` at the end of line can be replaced without change of meani The syntax changes allowing this are as follows: -```ebnf -Template ::= InheritClauses [colonEol] [TemplateBody] ; -EnumDef ::= id ClassConstr InheritClauses [colonEol] EnumBody ; -Packaging ::= ‘package’ QualId [nl | colonEol] ‘{’ TopStatSeq ‘}’ ; -SimpleExpr ::= ‘new’ ConstrApp {‘with’ ConstrApp} [[colonEol] TemplateBody] ; +``` +Template ::= InheritClauses [colonEol] [TemplateBody] +EnumDef ::= id ClassConstr InheritClauses [colonEol] EnumBody +Packaging ::= ‘package’ QualId [nl | colonEol] ‘{’ TopStatSeq ‘}’ +SimpleExpr ::= ‘new’ ConstrApp {‘with’ ConstrApp} [[colonEol] TemplateBody] ``` Here, `colonEol` stands for ": at end of line", as described above. @@ -243,6 +243,38 @@ case 5 => print("V") println(".") ``` +### Using Indentation to Signal Statement Continuation + +Indentation is used in some situations to decide whether to insert a virtual semicolon between +two consecutive lines or to treat them as one statement. Virtual semicolon insertion is +suppressed if the second line is indented more relative to the first one, and either the second line +starts with "`(`", "`[`", or "`{`" or the first line ends with `return`. Examples: + +```scala +f(x + 1) + (2, 3) // equivalent to `f(x + 1)(2, 3)` + +g(x + 1) +(2, 3) // equivalent to `g(x + 1); (2, 3)` + +h(x + 1) + {} // equivalent to `h(x + 1){}` + +i(x + 1) +{} // equivalent to `i(x + 1); {}` + +if x < 0 then return + a + b // equivalent to `if x < 0 then return a + b` + +if x < 0 then return +println(a + b) // equivalent to `if x < 0 then return; println(a + b)` +``` +In Scala 2, a line starting with "`{`" always continues the function call on the preceding line, +irrespective of indentation, whereas a virtual semicolon is inserted in all other cases. +The Scala-2 behavior is retained under source `-no-indent` or `-source 3.0-migration`. + + + ### The End Marker Indentation-based syntax has many advantages over other conventions. But one possible problem is that it makes it hard to discern when a large indentation region ends, since there is no specific token that delineates the end. Braces are not much better since a brace by itself also contains no information about what region is closed. @@ -340,13 +372,13 @@ If none of these criteria apply, it's often better to not use an end marker sinc #### Syntax -```ebnf -EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL ; +``` +EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ | ‘try’ - | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ ; -BlockStat ::= ... | EndMarker ; -TemplateStat ::= ... | EndMarker ; -TopStat ::= ... | EndMarker ; + | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ +BlockStat ::= ... | EndMarker +TemplateStat ::= ... | EndMarker +TopStat ::= ... | EndMarker ``` ### Example @@ -404,7 +436,7 @@ end IndentWidth ### Settings and Rewrites -Significant indentation is enabled by default. It can be turned off by giving any of the options `-no-indent`, `-old-syntax` and `-language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning. +Significant indentation is enabled by default. It can be turned off by giving any of the options `-no-indent`, `-old-syntax` and `-source 3.0-migration`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning. The Scala 3 compiler can rewrite source code to indented code and back. When invoked with options `-rewrite -indent` it will rewrite braces to diff --git a/docs/_docs/reference/other-new-features/opaques-details.md b/docs/_docs/reference/other-new-features/opaques-details.md index 9508f517710c..83608ca78dd3 100644 --- a/docs/_docs/reference/other-new-features/opaques-details.md +++ b/docs/_docs/reference/other-new-features/opaques-details.md @@ -6,9 +6,9 @@ movedTo: https://docs.scala-lang.org/scala3/reference/other-new-features/opaques ## Syntax -```ebnf +``` Modifier ::= ... - | ‘opaque’ ; + | ‘opaque’ ``` `opaque` is a [soft modifier](../soft-modifier.md). It can still be used as a normal identifier when it is not in front of a definition keyword. diff --git a/docs/_docs/usage/coverage.md b/docs/_docs/usage/coverage.md new file mode 100644 index 000000000000..2e24b08ea29e --- /dev/null +++ b/docs/_docs/usage/coverage.md @@ -0,0 +1,61 @@ +--- +layout: doc-page +title: "Code Coverage for Scala 3" +--- + +## Instrument code for coverage analysis + +[PR#13880](https://github.com/lampepfl/dotty/pull/13880) has implemented code coverage support for Dotty. +In general, code coverage works in three steps: +1. The program is "instrumented" at compilation time: code is inserted to record which statement are called. This does not change the behavior of the program. Also, a list of all the coverable statements is produced. +2. The program is run, usually by unit tests, and the instrumentation code saves its measurements. +3. Some tool processes the data to generate a fancy coverage report, for instance a web page. + +In Scala 2, all these steps were performed by external tools. In particular, step 1 was implemented by a compiler plugin. + +In Scala 3, the compiler itself takes care of step 1. To use this feature, add the compile option `-coverage-out:DIR`, where `DIR` is the destination of the measurement files. + +You can also set `-sourceroot:PATHS_ROOT` to customize how the path of your source files are resolved. +Note that `-sourceroot` also sets the root path of the SemanticDB files. + +## How-to with sbt + +For now, the Scoverage sbt plugin doesn't apply the above options automatically. +However, you can easily do it yourself, and use the plugin to generate user-friendly reports. + +1. Add the scoverage sbt plugin by appending this line to your `project/plugins.sbt` +```scala +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0-M4") +``` + +2. Compile your project with +```scala +Compile/compile/scalacOptions += + s"-coverage-out:target/scala-${scalaVersion.value}/scoverage-data" +``` + +2. Run the tests: `sbt test` +3. Generate xml and html reports: `sbt coverageReport` + +## Details: how the code is instrumented + +When the `-coverage-out` option is set, a new phase `instrumentCoverage` runs, just before `firstTransform`. +For a carefully selected list of tree types, it adds a call to `scala.runtime.Invoker.invoked(statementId, DIR)`. + +For instance, this code: +``` +def method() = + println(f()) +``` + +with `-coverage-out:target/cov` be turned to +``` +def method() = + Invoker.invoked(2, "target/cov") + println({ + Invoker.invoked(1, "target/cov") + f() + }) +``` + +At the end of the phase, the list of all the instrumented statements is serialized to the file `DIR/scoverage.coverage`. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 0f6ed6bf935d..95786121e8f4 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -147,6 +147,7 @@ subsection: - page: reference/experimental/named-typeargs-spec.md - page: reference/experimental/numeric-literals.md - page: reference/experimental/explicit-nulls.md + - page: reference/experimental/main-annotation.md - page: reference/experimental/cc.md - page: reference/experimental/tupled-function.md - page: reference/syntax.md @@ -192,5 +193,6 @@ subsection: - page: internals/type-system.md - page: internals/dotty-internals-1-notes.md - page: internals/debug-macros.md + - page: internals/gadts.md - page: release-notes-0.1.2.md hidden: true diff --git a/library/src/scala/annotation/MainAnnotation.scala b/library/src/scala/annotation/MainAnnotation.scala new file mode 100644 index 000000000000..9d2f5362ba15 --- /dev/null +++ b/library/src/scala/annotation/MainAnnotation.scala @@ -0,0 +1,126 @@ +package scala.annotation + +/** MainAnnotation provides the functionality for a compiler-generated main class. + * It links a compiler-generated main method (call it compiler-main) to a user + * written main method (user-main). + * The protocol of calls from compiler-main is as follows: + * + * - create a `command` with the command line arguments, + * - for each parameter of user-main, a call to `command.argGetter`, + * or `command.varargGetter` if is a final varargs parameter, + * - a call to `command.run` with the closure of user-main applied to all arguments. + * + * Example: + * ```scala + * /** Sum all the numbers + * * + * * @param first Fist number to sum + * * @param rest The rest of the numbers to sum + * */ + * @myMain def sum(first: Int, second: Int = 0, rest: Int*): Int = first + second + rest.sum + * ``` + * generates + * ```scala + * object foo { + * def main(args: Array[String]): Unit = { + * val mainAnnot = new myMain() + * val info = new Info( + * name = "foo.main", + * documentation = "Sum all the numbers", + * parameters = Seq( + * new Parameter("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum"), + * new Parameter("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum") + * ) + * ) + * val mainArgsOpt = mainAnnot.command(info, args) + * if mainArgsOpt.isDefined then + * val mainArgs = mainArgsOpt.get + * val args0 = mainAnnot.argGetter[Int](info.parameters(0), mainArgs(0), None) // using parser Int + * val args1 = mainAnnot.argGetter[Int](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) // using parser Int + * val args2 = mainAnnot.varargGetter[Int](info.parameters(2), mainArgs.drop(2)) // using parser Int + * mainAnnot.run(() => sum(args0(), args1(), args2()*)) + * } + * } + * ``` + * + * @param Parser The class used for argument string parsing and arguments into a `T` + * @param Result The required result type of the main method. + * If this type is Any or Unit, any type will be accepted. + */ +@experimental +trait MainAnnotation[Parser[_], Result] extends StaticAnnotation: + import MainAnnotation.{Info, Parameter} + + /** Process the command arguments before parsing them. + * + * Return `Some` of the sequence of arguments that will be parsed to be passed to the main method. + * This sequence needs to have the same length as the number of parameters of the main method (i.e. `info.parameters.size`). + * If there is a varags parameter, then the sequence must be at least of length `info.parameters.size - 1`. + * + * Returns `None` if the arguments are invalid and parsing and run should be stopped. + * + * @param info The information about the command (name, documentation and info about parameters) + * @param args The command line arguments + */ + def command(info: Info, args: Seq[String]): Option[Seq[String]] + + /** The getter for the `idx`th argument of type `T` + * + * @param idx The index of the argument + * @param defaultArgument Optional lambda to instantiate the default argument + */ + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using Parser[T]): () => T + + /** The getter for a final varargs argument of type `T*` */ + def varargGetter[T](param: Parameter, args: Seq[String])(using Parser[T]): () => Seq[T] + + /** Run `program` if all arguments are valid if all arguments are valid + * + * @param program A function containing the call to the main method and instantiation of its arguments + */ + def run(program: () => Result): Unit + +end MainAnnotation + +@experimental +object MainAnnotation: + + /** Information about the main method + * + * @param name The name of the main method + * @param documentation The documentation of the main method without the `@param` documentation (see Parameter.documentaion) + * @param parameters Information about the parameters of the main method + */ + final class Info( + val name: String, + val documentation: String, + val parameters: Seq[Parameter], + ): + + /** If the method ends with a varargs parameter */ + def hasVarargs: Boolean = parameters.nonEmpty && parameters.last.isVarargs + + end Info + + /** Information about a parameter of a main method + * + * @param name The name of the parameter + * @param typeName The name of the parameter's type + * @param hasDefault If the parameter has a default argument + * @param isVarargs If the parameter is a varargs parameter (can only be true for the last parameter) + * @param documentation The documentation of the parameter (from `@param` documentation in the main method) + * @param annotations The annotations of the parameter that extend `ParameterAnnotation` + */ + final class Parameter( + val name: String, + val typeName: String, + val hasDefault: Boolean, + val isVarargs: Boolean, + val documentation: String, + val annotations: Seq[ParameterAnnotation], + ) + + /** Marker trait for annotations that will be included in the Parameter annotations. */ + trait ParameterAnnotation extends StaticAnnotation + +end MainAnnotation diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index bf7922239314..996fe3ff8da2 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -4,7 +4,7 @@ package scala.quoted * * `Expr` has extension methods that are defined in `scala.quoted.Quotes`. */ -abstract class Expr[+T] private[scala] +abstract class Expr[+T] private[scala] () /** Constructors for expressions */ object Expr { diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 862a9def8dcb..08898b4f7a1c 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -2812,6 +2812,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val AppliedType` */ trait AppliedTypeModule { this: AppliedType.type => + /** Applied the type constructor `T` to a list of type arguments `T_1,..,T_n` to create `T[T_1,..,T_n]` */ + @experimental + def apply(tycon: TypeRepr, args: List[TypeRepr]): AppliedType def unapply(x: AppliedType): (TypeRepr, List[TypeRepr]) } @@ -3607,7 +3610,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * def decls(cls: Symbol): List[Symbol] = * List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) * - * val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None) + * val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None) * val fooSym = cls.declaredMethod("foo").head * * val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling foo")}.asTerm)) diff --git a/library/src/scala/quoted/runtime/QuoteUnpickler.scala b/library/src/scala/quoted/runtime/QuoteUnpickler.scala index 2d8ef54eb9e6..63e62658cbb4 100644 --- a/library/src/scala/quoted/runtime/QuoteUnpickler.scala +++ b/library/src/scala/quoted/runtime/QuoteUnpickler.scala @@ -7,11 +7,28 @@ trait QuoteUnpickler: /** Unpickle `repr` which represents a pickled `Expr` tree, * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.0.x and 3.1.x */ def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], Quotes) => Expr[?]): scala.quoted.Expr[T] + /** Unpickle `repr` which represents a pickled `Expr` tree, + * replacing splice nodes with `holes`. + * + * Generated for code compiled with Scala 3.2.0+ + */ + def unpickleExprV2[T](pickled: String | List[String], types: Null | Seq[Type[?]], termHole: Null | ((Int, Seq[Type[?] | Expr[Any]], Quotes) => Expr[?])): scala.quoted.Expr[T] + /** Unpickle `repr` which represents a pickled `Type` tree, * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.0.x and 3.1.x */ def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], Quotes) => Expr[?]): scala.quoted.Type[T] + /** Unpickle `repr` which represents a pickled `Type` tree, + * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.2.0+ + */ + def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Null | Seq[Type[?]]): scala.quoted.Type[T] diff --git a/library/src/scala/runtime/coverage/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala new file mode 100644 index 000000000000..ee37a477cbe6 --- /dev/null +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -0,0 +1,55 @@ +package scala.runtime.coverage + +import scala.collection.mutable.{BitSet, AnyRefMap} +import scala.collection.concurrent.TrieMap +import java.nio.file.Files +import java.io.FileWriter +import java.io.File +import scala.annotation.internal.sharable + +@sharable // avoids false positive by -Ycheck-reentrant +object Invoker { + private val runtimeUUID = java.util.UUID.randomUUID() + + private val MeasurementsPrefix = "scoverage.measurements." + private val threadFiles = new ThreadLocal[AnyRefMap[String, FileWriter]] + private val dataDirToSet = TrieMap.empty[String, BitSet] + + /** We record that the given id has been invoked by appending its id to the coverage data file. + * + * This will happen concurrently on as many threads as the application is using, so we use one + * file per thread, named for the thread id. + * + * This method is not thread-safe if the threads are in different JVMs, because the thread IDs + * may collide. You may not use `scoverage` on multiple processes in parallel without risking + * corruption of the measurement file. + * + * @param id + * the id of the statement that was invoked + * @param dataDir + * the directory where the measurement data is held + */ + def invoked(id: Int, dataDir: String): Unit = + val set = dataDirToSet.getOrElseUpdate(dataDir, BitSet.empty) + if !set.contains(id) then + val added = set.synchronized { + set.add(id) + } + if added then + var writers = threadFiles.get() + if writers == null then + writers = AnyRefMap.empty + threadFiles.set(writers) + val writer = writers.getOrElseUpdate( + dataDir, + FileWriter(measurementFile(dataDir), true) + ) + writer.write(Integer.toString(id)) + writer.write('\n') + writer.flush() + + def measurementFile(dataDir: String): File = new File( + dataDir, + MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.nn.getId + ) +} diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 7c8c116a106e..683155fb0e48 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -149,10 +149,13 @@ object language: object `3.0` /** Set source version to 3.1-migration. + * + * This is a no-op, and should not be used. A syntax error will be reported upon import. * * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] */ @compileTimeOnly("`3.1-migration` can only be used at compile time in import statements") + @deprecated("`3.1-migration` is not valid, use `3.1` instead", since = "3.2") object `3.1-migration` /** Set source version to 3.1 diff --git a/project/Build.scala b/project/Build.scala index ae051b8aaad4..e925b8c84324 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -57,9 +57,9 @@ object DottyJSPlugin extends AutoPlugin { object Build { import ScaladocConfigs._ - val referenceVersion = "3.1.2" + val referenceVersion = "3.1.3-RC2" - val baseVersion = "3.1.3-RC1" + val baseVersion = "3.2.0-RC1" // Versions used by the vscode extension to create a new project // This should be the latest published releases. @@ -439,6 +439,8 @@ object Build { case CompatMode.BinaryCompatible => "backward" case CompatMode.SourceAndBinaryCompatible => "both" }), + + mimaExcludeAnnotations += "scala.annotation.experimental", ) /** Projects -------------------------------------------------------------- */ @@ -519,7 +521,7 @@ object Build { // get libraries onboard libraryDependencies ++= Seq( - "org.scala-lang.modules" % "scala-asm" % "9.2.0-scala-1", // used by the backend + "org.scala-lang.modules" % "scala-asm" % "9.3.0-scala-1", // used by the backend Dependencies.oldCompilerInterface, // we stick to the old version to avoid deprecation warnings "org.jline" % "jline-reader" % "3.19.0", // used by the REPL "org.jline" % "jline-terminal" % "3.19.0", @@ -572,8 +574,8 @@ object Build { ) }, - javaOptions += ( - s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}" + javaOptions ++= Seq( + s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}", ), testCompilation := Def.inputTaskDyn { @@ -583,7 +585,8 @@ object Build { s""" |usage: testCompilation [--help] [--from-tasty] [--update-checkfiles] [] | - |By default runs tests in dotty.tools.dotc.*CompilationTests excluding tests tagged with dotty.SlowTests. + |By default runs tests in dotty.tools.dotc.*CompilationTests and dotty.tools.dotc.coverage.*, + |excluding tests tagged with dotty.SlowTests. | | --help show this message | --from-tasty runs tests in dotty.tools.dotc.FromTastyTests @@ -598,7 +601,7 @@ object Build { val updateCheckfile = args.contains("--update-checkfiles") val fromTasty = args.contains("--from-tasty") val args1 = if (updateCheckfile | fromTasty) args.filter(x => x != "--update-checkfiles" && x != "--from-tasty") else args - val test = if (fromTasty) "dotty.tools.dotc.FromTastyTests" else "dotty.tools.dotc.*CompilationTests" + val test = if (fromTasty) "dotty.tools.dotc.FromTastyTests" else "dotty.tools.dotc.*CompilationTests dotty.tools.dotc.coverage.*" val cmd = s" $test -- --exclude-categories=dotty.SlowTests" + (if (updateCheckfile) " -Ddotty.tests.updateCheckfiles=TRUE" else "") + (if (args1.nonEmpty) " -Ddotty.tests.filter=" + args1.mkString(" ") else "") @@ -1394,6 +1397,7 @@ object Build { .add(OutputDir("scaladoc/output/reference")) .add(SiteRoot(s"${temp.getAbsolutePath}/docs")) .add(ProjectName("Scala 3 Reference")) + .add(ProjectVersion("3.1.2")) // TODO: Change that later to the current version tag. (This must happen on first forward this branch to stable release tag) .remove[VersionsDictionaryUrl] .add(SourceLinks(List( dottySrcLink(referenceVersion, temp.getAbsolutePath + "=") @@ -1407,7 +1411,7 @@ object Build { val outputDir = languageReferenceConfig.value.get[OutputDir].get.value val expectedLinksFile = (file("project") / "scripts" / "expected-links" / "reference-expected-links.txt").toString import _root_.scala.sys.process._ - s"$script $outputDir $expectedLinksFile" ! + s"$script $outputDir $expectedLinksFile".! } } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 4c90fd39f361..ef450919d806 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,32 +3,16 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( - // Experimental APIs that can be added in 3.2.0 or later - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeReprMethods.typeArgs"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromProductTyped"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromTuple"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), + // APIs that must be added in 3.2.0 + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), - // TupledFunction - ProblemFilters.exclude[MissingClassProblem]("scala.runtime.TupledFunctions"), - ProblemFilters.exclude[MissingClassProblem]("scala.runtime.TupledFunctions$"), - ProblemFilters.exclude[MissingClassProblem]("scala.util.TupledFunction"), - ProblemFilters.exclude[MissingClassProblem]("scala.util.TupledFunction$"), - - // Private inner classes, but we emit all classes as public in Java bytecode - ProblemFilters.exclude[InaccessibleClassProblem]("scala.quoted.FromExpr$PrimitiveFromExpr"), - ProblemFilters.exclude[InaccessibleClassProblem]("scala.quoted.Type$ValueOf$"), - ProblemFilters.exclude[InaccessibleClassProblem]("scala.reflect.Selectable$DefaultSelectable"), + // Experimental `MainAnnotation` APIs. Can be added in 3.3.0 or later. + // MiMa bug: classes nested in an experimental object should be ignored + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Info"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Parameter"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$ParameterAnnotation"), ) } diff --git a/project/plugins.sbt b/project/plugins.sbt index 1716bb51b7f1..f0b13c6cb7c4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,7 +2,7 @@ // // e.g. addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.9.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.10") @@ -14,4 +14,4 @@ addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.0") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.0") diff --git a/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml index a8453449e73e..680b44d353d4 100644 --- a/project/resources/referenceReplacements/sidebar.yml +++ b/project/resources/referenceReplacements/sidebar.yml @@ -127,6 +127,7 @@ subsection: - page: reference/experimental/named-typeargs-spec.md - page: reference/experimental/numeric-literals.md - page: reference/experimental/explicit-nulls.md + - page: reference/experimental/main-annotation.md - page: reference/experimental/cc.md - page: reference/syntax.md - title: Language Versions diff --git a/project/scripts/expected-links/reference-expected-links.txt b/project/scripts/expected-links/reference-expected-links.txt index 737267576c6e..f51727b7b432 100644 --- a/project/scripts/expected-links/reference-expected-links.txt +++ b/project/scripts/expected-links/reference-expected-links.txt @@ -68,6 +68,7 @@ ./experimental/erased-defs.html ./experimental/explicit-nulls.html ./experimental/index.html +./experimental/main-annotation.html ./experimental/named-typeargs-spec.html ./experimental/named-typeargs.html ./experimental/numeric-literals.html diff --git a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java index 075efd5b53e6..6b3c25e2e27c 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java +++ b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java @@ -123,7 +123,13 @@ public Optional pointerSpace() { @Override public String toString() { - return pos.toString(); + String path = sourcePath().orElse(""); + Optional l = line(); + Integer column = pointer().orElse(1); + if (l.isPresent()) + return String.format("%s:%d:%d", path, l.get(), column); + else + return path; } @Override diff --git a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala index d6cebbfe3cac..0b580e1cb77a 100644 --- a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala +++ b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala @@ -15,16 +15,6 @@ object CommunityBuildPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin override def trigger = allRequirements - object autoImport { - val isForwardCompatProject = settingKey[Boolean]("Is it a project used for testing forward binary compatibility?") - } - - import autoImport._ - - override val globalSettings: Seq[Setting[_]] = Seq( - isForwardCompatProject := false - ) - override val projectSettings: Seq[Setting[_]] = Seq( publishLocal := Def.taskDyn { val pubLocalResult = publishLocal.value @@ -33,30 +23,14 @@ object CommunityBuildPlugin extends AutoPlugin { CommunityBuildDependencies.publish(projectID.value) pubLocalResult } - }.value, - projectID := { - val id = projectID.value - if (isForwardCompatProject.value) { - val revision = if (id.revision.endsWith("-SNAPSHOT")) - id.revision.replace("-SNAPSHOT", "-forward-compat-SNAPSHOT") - else - id.revision + "-forward-compat" - id.withRevision(revision) - } else - id - } + }.value ) override val buildSettings: Seq[Setting[_]] = Seq( dependencyOverrides ++= { - if (scalaVersion.value.startsWith("3.")) { - val forwardCompatFilter: ModuleID => Boolean = if (isForwardCompatProject.value) (_.revision.contains("-forward-compat")) else (!_.revision.contains("-forward-compat")) - val stdlibOverrides = Seq( - scalaOrganization.value %% "scala3-library" % scalaVersion.value, - scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value - ) - CommunityBuildDependencies.allOverrides(sLog.value).filter(forwardCompatFilter) ++ stdlibOverrides - } else Nil + if (scalaVersion.value.startsWith("3.")) + CommunityBuildDependencies.allOverrides(sLog.value) + else Nil } ) } diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/app/Main.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/app/Main.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/app/Main.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/app/Main.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/build.sbt b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/build.sbt similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/build.sbt rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/build.sbt diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/lib/Top.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/lib/Top.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/lib/Top.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/lib/Top.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/project/DottyInjectedPlugin.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/project/DottyInjectedPlugin.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/project/DottyInjectedPlugin.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/test b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/test similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/test rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/test diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala similarity index 89% rename from tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala rename to sbt-test/scala3-compat/macros-backward-3.0/app/App.scala index 8c0a8004b9cf..ce7bf1b45b13 100644 --- a/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala +++ b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala @@ -1,4 +1,6 @@ -import Macros.* +package app + +import lib.* def powerTest(x: Double): Unit = power(x, 0) diff --git a/sbt-test/scala3-compat/macros-backward-3.0/build.sbt b/sbt-test/scala3-compat/macros-backward-3.0/build.sbt new file mode 100644 index 000000000000..6e5bd200ecb1 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/build.sbt @@ -0,0 +1,13 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := "3.0.2", + scalacOptions ++= checkOptions, + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalacOptions ++= checkOptions, + ) diff --git a/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala b/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-backward-3.0/test b/sbt-test/scala3-compat/macros-backward-3.0/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/test @@ -0,0 +1 @@ +> app/compile diff --git a/tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala similarity index 89% rename from tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala rename to sbt-test/scala3-compat/macros-backward-3.1/app/App.scala index 8c0a8004b9cf..ce7bf1b45b13 100644 --- a/tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala +++ b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala @@ -1,4 +1,6 @@ -import Macros.* +package app + +import lib.* def powerTest(x: Double): Unit = power(x, 0) diff --git a/sbt-test/scala3-compat/macros-backward-3.1/build.sbt b/sbt-test/scala3-compat/macros-backward-3.1/build.sbt new file mode 100644 index 000000000000..024e9be97524 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/build.sbt @@ -0,0 +1,13 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := "3.1.1", + scalacOptions ++= checkOptions, + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalacOptions ++= checkOptions, + ) diff --git a/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala b/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-backward-3.1/test b/sbt-test/scala3-compat/macros-backward-3.1/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/test @@ -0,0 +1 @@ +> app/compile diff --git a/scaladoc-testcases/src/_docs/tests/Adoc.scala b/scaladoc-testcases/src/docs/tests/Adoc.scala similarity index 59% rename from scaladoc-testcases/src/_docs/tests/Adoc.scala rename to scaladoc-testcases/src/docs/tests/Adoc.scala index 04b2c3918023..af93f9eddfa0 100644 --- a/scaladoc-testcases/src/_docs/tests/Adoc.scala +++ b/scaladoc-testcases/src/docs/tests/Adoc.scala @@ -1,4 +1,4 @@ -package _docs.tests +package docs.tests class Adoc: def foo = 123 diff --git a/scaladoc-testcases/src/example/JekyllIncompat.scala b/scaladoc-testcases/src/example/JekyllIncompat.scala new file mode 100644 index 000000000000..65c735804d57 --- /dev/null +++ b/scaladoc-testcases/src/example/JekyllIncompat.scala @@ -0,0 +1,5 @@ +package example + +package jekyllIncompat: + object #~ + object ~>~ diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala index 64c072aa3de3..366b5b8c162c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala @@ -17,6 +17,8 @@ import java.io.PrintStream import scala.io.Codec import java.net.URL import scala.util.Try +import scala.collection.mutable +import dotty.tools.scaladoc.util.Check.checkJekyllIncompatPath type CompilerContext = dotty.tools.dotc.core.Contexts.Context @@ -90,3 +92,18 @@ case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext): val externalDocumentationLinks = args.externalMappings + + + // We collect and report any generated files incompatible with Jekyll + private lazy val jekyllIncompatLinks = mutable.HashSet[String]() + + def checkPathCompat(path: Seq[String]): Unit = + if checkJekyllIncompatPath(path) then jekyllIncompatLinks.add(path.mkString("/")) + + def reportPathCompatIssues(): Unit = + if !jekyllIncompatLinks.isEmpty then + report.warning( + s"""Following generated file paths might not be compatible with Jekyll: + |${jekyllIncompatLinks.mkString("\n")} + |If using GitHub Pages consider adding a \".nojekyll\" file. + """.stripMargin)(using compilerContext) diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index 0b746f19035b..9d263441ace1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -229,5 +229,6 @@ object Scaladoc: given docContext: DocContext = new DocContext(args, ctx) val module = ScalaModuleProvider.mkModule() new dotty.tools.scaladoc.renderers.HtmlRenderer(module.rootPackage, module.members).render() + docContext.reportPathCompatIssues() report.inform("generation completed successfully") docContext diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala index 9490693fb759..12615671e538 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala @@ -47,6 +47,7 @@ trait Locations(using ctx: DocContext): case "" :: tail => "_empty_" :: tail case other => other if ctx.args.apiSubdirectory then "api" :: fqn else fqn + ctx.checkPathCompat(path) cache.put(dri, path) path case cached => cached diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala index 6d177e113c24..4ce81450afbb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala @@ -120,7 +120,7 @@ abstract class Renderer(rootPackage: Member, val members: Map[DRI, Member], prot val all = navigablePage +: redirectPages // We need to check for conflicts only if we have top-level member called docs val hasPotentialConflict = - rootPackage.members.exists(m => m.name.startsWith("_docs")) + rootPackage.members.exists(m => m.name.startsWith("docs")) if hasPotentialConflict then def walk(page: Page): Unit = diff --git a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala index 48c2acb745af..51ee9e30e933 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala @@ -23,7 +23,12 @@ class StaticSiteContext( val docsPath = root.toPath.resolve("_docs") val blogPath = root.toPath.resolve("_blog") - val relativizeFrom = if args.apiSubdirectory then docsPath else root.toPath + def relativize(path: Path): Path = + if args.apiSubdirectory then + docsPath.relativize(path) + else + val relativised = docsPath.relativize(path) + Paths.get("docs").resolve(relativised) val siteExtensions = Set(".html", ".md") @@ -47,7 +52,7 @@ class StaticSiteContext( val redirectFrom = loadedTemplate.templateFile.settings.getOrElse("page", Map.empty).asInstanceOf[Map[String, Object]].get("redirectFrom") def redirectToTemplate(redirectFrom: String) = val path = if redirectFrom.startsWith("/") - then relativizeFrom.resolve(redirectFrom.drop(1)) + then docsPath.resolve(redirectFrom.drop(1)) else loadedTemplate.file.toPath.resolveSibling(redirectFrom) val driFrom = driFor(path) val driTo = driFor(loadedTemplate.file.toPath) @@ -93,7 +98,7 @@ class StaticSiteContext( } def driFor(dest: Path): DRI = - val rawFilePath = relativizeFrom.relativize(dest) + val rawFilePath = relativize(dest) val pageName = dest.getFileName.toString val dotIndex = pageName.lastIndexOf('.') diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 7d5886193c6b..1e855e6e541a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -96,7 +96,9 @@ trait ClassLikeSupport: getSupertypesGraph(LinkToType(selfSignature, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)), unpackTreeToClassDef(classDef).parents) ) - val baseMember = mkMember(classDef.symbol, kindForClasslike(classDef), selfSignature)( + val kind = if intrinsicClassDefs.contains(classDef.symbol) then Kind.Class(Nil, Nil) else kindForClasslike(classDef) + + val baseMember = mkMember(classDef.symbol, kind, selfSignature)( modifiers = modifiers, graph = graph, deprecated = classDef.symbol.isDeprecated(), @@ -253,8 +255,9 @@ trait ClassLikeSupport: } def getParentsAsTreeSymbolTuples: List[(Tree, Symbol)] = - for - parentTree <- c.parents if isValidPos(parentTree.pos) // We assume here that order is correct + if noPosClassDefs.contains(c.symbol) then Nil + else for + parentTree <- c.parents if parentTree.pos.start != parentTree.pos.end // We assume here that order is correct parentSymbol = parentTree match case t: TypeTree => t.tpe.typeSymbol case tree if tree.symbol.isClassConstructor => tree.symbol.owner @@ -482,7 +485,7 @@ trait ClassLikeSupport: def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = - val baseTypeRepr = memberInfo(c, symbol) + val baseTypeRepr = typeForClass(c).memberType(symbol) def isSyntheticEvidence(name: String) = if !name.startsWith(NameKinds.EvidenceParamName.separator) then false else @@ -551,4 +554,4 @@ trait ClassLikeSupport: if parameters(0).symbol.flags.is(Flags.Given) then "using " else if parameters(0).symbol.flags.is(Flags.Implicit) then "implicit " else "" - else "" \ No newline at end of file + else "" diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index 735d9c74d733..8b98e40fd6b0 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -5,17 +5,6 @@ import scala.quoted._ object SyntheticsSupport: - extension (using Quotes)(t: reflect.TypeRepr) - - def isCompiletimeAppliedType: Boolean = t.hackIsCompiletimeAppliedType(t) - - private def hackIsCompiletimeAppliedType(rtpe: reflect.TypeRepr): Boolean = - import dotty.tools.dotc - given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val tpe = rtpe.asInstanceOf[dotc.core.Types.Type] - ctx.definitions.isCompiletimeAppliedType(tpe.typeSymbol) - end extension - extension (using Quotes)(s: reflect.Symbol) def isSyntheticFunc: Boolean = import reflect._ @@ -29,39 +18,32 @@ object SyntheticsSupport: import reflect._ s.flags.is(Flags.Opaque) - def isInfix: Boolean = hackIsInfix(s) - def getmembers: List[reflect.Symbol] = hackGetmembers(s) end extension - def isValidPos(using Quotes)(pos: reflect.Position) = - if hackExists(pos) then pos.start != pos.end else false - def isSyntheticField(using Quotes)(c: reflect.Symbol) = import reflect._ c.flags.is(Flags.CaseAccessor) || (c.flags.is(Flags.Module) && !c.flags.is(Flags.Given)) def constructorWithoutParamLists(using Quotes)(c: reflect.ClassDef): Boolean = - !isValidPos(c.constructor.pos) || { + c.constructor.pos.start == c.constructor.pos.end || { val end = c.constructor.pos.end val typesEnd = c.constructor.leadingTypeParams.lastOption.fold(end - 1)(_.pos.end) val classDefTree = c.constructor.show c.constructor.leadingTypeParams.nonEmpty && end <= typesEnd + 1 } - // TODO: #49 Remove it after TASTY-Reflect release with published flag Extension - private def hackIsInfix(using Quotes)(rsym: reflect.Symbol): Boolean = { - import reflect._ - import dotty.tools.dotc - given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val sym = rsym.asInstanceOf[dotc.core.Symbols.Symbol] - ctx.definitions.isInfix(sym) - } + def getSupertypes(using Quotes)(c: reflect.ClassDef) = + c.symbol.typeRef.baseClasses.map(b => b -> c.symbol.typeRef.baseType(b)).tail + + def typeForClass(using Quotes)(c: reflect.ClassDef): reflect.TypeRepr = + c.symbol.typeRef.appliedTo(c.symbol.typeMembers.filter(_.isTypeParam).map(_.typeRef)) + /* We need there to filter out symbols with certain flagsets, because these symbols come from compiler and TASTY can't handle them well. - They are valdefs that describe case companion objects and cases from enum. - TASTY crashed when calling _.tree on them. - */ + They are valdefs that describe case companion objects and cases from enum. + TASTY crashed when calling _.tree on them. + */ private def hackGetmembers(using Quotes)(rsym: reflect.Symbol): List[reflect.Symbol] = { import reflect._ import dotty.tools.dotc @@ -75,40 +57,3 @@ object SyntheticsSupport: sym.asInstanceOf[Symbol] }.toList } - - private def hackGetSupertypes(using Quotes)(rdef: reflect.ClassDef) = { - import reflect._ - import dotty.tools.dotc - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val classdef = rdef.asInstanceOf[dotc.ast.tpd.TypeDef] - val ref = classdef.symbol.info.asInstanceOf[dotc.core.Types.ClassInfo].appliedRef - val baseTypes: List[(dotc.core.Symbols.Symbol, dotc.core.Types.Type)] = - ref.baseClasses.map(b => b -> ref.baseType(b)) - baseTypes.asInstanceOf[List[(Symbol, TypeRepr)]] - } - - private def hackExists(using Quotes)(rpos: reflect.Position) = { - import reflect._ - import dotty.tools.dotc - import dotty.tools.dotc.util.Spans._ - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val pos = rpos.asInstanceOf[dotc.util.SourcePosition] - pos.exists - } - - def getSupertypes(using Quotes)(c: reflect.ClassDef) = hackGetSupertypes(c).tail - - def typeForClass(using Quotes)(c: reflect.ClassDef): reflect.TypeRepr = - import reflect._ - import dotty.tools.dotc - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val cSym = c.symbol.asInstanceOf[dotc.core.Symbols.Symbol] - cSym.typeRef.appliedTo(cSym.typeParams.map(_.typeRef)).asInstanceOf[TypeRepr] - - def memberInfo(using Quotes)(c: reflect.ClassDef, symbol: reflect.Symbol): reflect.TypeRepr = - import reflect._ - import dotty.tools.dotc - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - typeForClass(c).asInstanceOf[dotc.core.Types.Type] - .memberInfo(symbol.asInstanceOf[dotc.core.Symbols.Symbol]) - .asInstanceOf[TypeRepr] diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 76416c793b1d..b78dab1e28df 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -123,27 +123,17 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspe topLevels ++= parser.parseRootTree(treeRoot) } - val defn = ctx.compilerContext.definitions - if ctx.args.documentSyntheticTypes then - val intrinsicClassDefs = Seq( - defn.AnyClass, - defn.MatchableClass, - defn.AnyKindClass, - defn.AnyValClass, - defn.NullClass, - defn.NothingClass, - defn.SingletonClass, - defn.andType, - defn.orType, - ).map { s => - "scala" -> s.asInstanceOf[parser.qctx.reflect.Symbol].tree.match { - case cd: parser.qctx.reflect.ClassDef => parser.parseClasslike(cd) - case td: parser.qctx.reflect.TypeDef => parser.parseTypeDef(td) - } + import parser.qctx.reflect._ + val intrinsicTypeDefs = parser.intrinsicTypeDefs.toSeq.map { s => + "scala" -> parser.parseTypeDef(s.tree.asInstanceOf[TypeDef]) + } + val intrinsicClassDefs = parser.intrinsicClassDefs.toSeq.map { s => + "scala" -> parser.parseClasslike(s.tree.asInstanceOf[ClassDef]) } topLevels ++= intrinsicClassDefs - val scalaPckg = defn.ScalaPackageVal.asInstanceOf[parser.qctx.reflect.Symbol] + topLevels ++= intrinsicTypeDefs + val scalaPckg = defn.ScalaPackage given parser.qctx.type = parser.qctx topLevels += "scala" -> Member(scalaPckg.fullName, scalaPckg.dri, Kind.Package) topLevels += mergeAnyRefAliasAndObject(parser) @@ -167,12 +157,13 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspe }.toList -> rootDoc def mergeAnyRefAliasAndObject(parser: TastyParser) = - val defn = ctx.compilerContext.definitions - val oM = parser.parseClasslike(defn.ObjectClass.asInstanceOf[parser.qctx.reflect.Symbol].tree.asInstanceOf[parser.qctx.reflect.ClassDef]) - val aM = parser.parseTypeDef(defn.AnyRefAlias.asInstanceOf[parser.qctx.reflect.Symbol].tree.asInstanceOf[parser.qctx.reflect.TypeDef]) + import parser.qctx.reflect._ + val javaLangObjectDef = defn.ObjectClass.tree.asInstanceOf[ClassDef] + val objectMembers = parser.extractPatchedMembers(javaLangObjectDef) + val aM = parser.parseTypeDef(defn.AnyRefClass.tree.asInstanceOf[TypeDef]) "scala" -> aM.copy( - kind = oM.kind, - members = oM.members + kind = Kind.Class(Nil, Nil), + members = objectMembers ) /** Parses a single Tasty compilation unit. */ case class TastyParser( @@ -187,6 +178,25 @@ case class TastyParser( private given qctx.type = qctx + val intrinsicClassDefs = Set( + defn.AnyClass, + defn.MatchableClass, + defn.ScalaPackage.typeMember("AnyKind"), + defn.AnyValClass, + defn.NullClass, + defn.NothingClass, + defn.ScalaPackage.typeMember("Singleton"), + ) + + val noPosClassDefs = intrinsicClassDefs ++ Set( + defn.ObjectClass, + defn.AnyRefClass + ) + + val intrinsicTypeDefs = Set( + defn.ScalaPackage.typeMember("&"), + defn.ScalaPackage.typeMember("|"), + ) def processTree[T](tree: Tree)(op: => T): Option[T] = try Option(op) catch case e: Exception => report.warning(throwableToString(e), tree.pos) diff --git a/scaladoc/src/dotty/tools/scaladoc/util/check.scala b/scaladoc/src/dotty/tools/scaladoc/util/check.scala new file mode 100644 index 000000000000..cbbeaded312e --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/util/check.scala @@ -0,0 +1,14 @@ +package dotty.tools.scaladoc.util + +object Check: + /** + * Jekyll (also used by GitHub Pages) by default makes a couple characters + * illegal to use in file name beginnings. + */ + def checkJekyllIncompatPath(path: Seq[String]): Boolean = + path.find( filename => + filename.matches("^~.*") + || filename.matches("^\\_.*") + || filename.matches("^\\..*") + || filename.matches("^\\#.*") + ).isDefined \ No newline at end of file diff --git a/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala b/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala index e64eac41ac66..fc215b1de229 100644 --- a/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala @@ -76,11 +76,11 @@ class ReportingTest: val docsRoot = testDocPath.resolve("conflicts-pages").toString checkReportedDiagnostics(_.copy( docsRoot = Some(docsRoot), - tastyFiles = tastyFiles("tests", rootPck = "_docs") + tastyFiles = tastyFiles("tests", rootPck = "docs") )){ diag => assertNoWarning(diag) val Seq(msg) = diag.errorMsgs.map(_.toLowerCase) - Seq("conflict","api", "static", "page", "_docs/tests/adoc.html") + Seq("conflict","api", "static", "page", "docs/tests/adoc.html") .foreach( word => Assert.assertTrue(s"Error message: $msg should contains $word", msg.contains(word)) ) diff --git a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala index 39bd7acf84f6..0a2d1891a3cc 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala @@ -44,5 +44,5 @@ class NavigationTest extends BaseHtmlTest: )), )) - testNavMenu("_docs/Adoc.html", topLevelNav) + testNavMenu("docs/Adoc.html", topLevelNav) } diff --git a/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala index 1b3c53aba3c0..c46875c7d0cd 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala @@ -32,13 +32,13 @@ class SiteGeneratationTest extends BaseHtmlTest: } def testDocPages()(using ProjectContext) = - checkFile("_docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) - checkFile("_docs/dir/index.html")(title = "A directory", header = "A directory", parents = Seq(projectName)) - checkFile("_docs/dir/nested.html")( + checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) + checkFile("docs/dir/index.html")(title = "A directory", header = "A directory", parents = Seq(projectName)) + checkFile("docs/dir/nested.html")( title = "Nested in a directory", header = "Nested in a directory", parents = Seq(projectName, "A directory")) def testDocIndexPage()(using ProjectContext) = - checkFile("_docs/index.html")(title = projectName, header = s"$projectName in header") + checkFile("docs/index.html")(title = projectName, header = s"$projectName in header") def testApiPages( mainTitle: String = "API", @@ -66,13 +66,13 @@ class SiteGeneratationTest extends BaseHtmlTest: testDocIndexPage() testApiPages() - withHtmlFile("_docs/Adoc.html"){ content => + withHtmlFile("docs/Adoc.html"){ content => content.assertAttr("p a","href", "../tests/site/SomeClass.html") } withHtmlFile("tests/site/SomeClass.html"){ content => content.assertAttr(".breadcrumbs a","href", - "../../_docs/index.html", "../../index.html", "../site.html", "SomeClass.html" + "../../docs/index.html", "../../index.html", "../site.html", "SomeClass.html" ) } } @@ -98,7 +98,7 @@ class SiteGeneratationTest extends BaseHtmlTest: @Test def staticLinking() = withGeneratedSite(testDocPath.resolve("static-links")){ - withHtmlFile("_docs/Adoc.html"){ content => + withHtmlFile("docs/Adoc.html"){ content => content.assertAttr("p a","href", "dir/html.html", "dir/name...with..dots..html", diff --git a/staging/src/scala/quoted/staging/QuoteCompiler.scala b/staging/src/scala/quoted/staging/QuoteCompiler.scala index 7b1899821f96..eee2dacdc5f5 100644 --- a/staging/src/scala/quoted/staging/QuoteCompiler.scala +++ b/staging/src/scala/quoted/staging/QuoteCompiler.scala @@ -16,7 +16,7 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types.ExprType import dotty.tools.dotc.quoted.PickledQuotes import dotty.tools.dotc.transform.Splicer.checkEscapedVariables -import dotty.tools.dotc.transform.{Inlining, Staging, PickleQuotes} +import dotty.tools.dotc.transform.{Inlining, Staging, Splicing, PickleQuotes} import dotty.tools.dotc.util.Spans.Span import dotty.tools.dotc.util.{SourceFile, NoSourcePosition} import dotty.tools.io.{Path, VirtualFile} @@ -41,6 +41,7 @@ private class QuoteCompiler extends Compiler: override protected def picklerPhases: List[List[Phase]] = List(new Inlining) :: List(new Staging) :: + List(new Splicing) :: List(new PickleQuotes) :: Nil diff --git a/tests/coverage/pos/Constructor.scala b/tests/coverage/pos/Constructor.scala new file mode 100644 index 000000000000..251370ec8e6e --- /dev/null +++ b/tests/coverage/pos/Constructor.scala @@ -0,0 +1,11 @@ +package covtest + +class C: + def f(x: Int) = x + def x = 1 + f(x) + +object O: + def g(y: Int) = y + def y = 1 + g(y) diff --git a/tests/coverage/pos/Constructor.scoverage.check b/tests/coverage/pos/Constructor.scoverage.check new file mode 100644 index 000000000000..b6223734b1a8 --- /dev/null +++ b/tests/coverage/pos/Constructor.scoverage.check @@ -0,0 +1,122 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +Constructor.scala +covtest +C +Class +covtest.C +f +28 +33 +3 +f +DefDef +false +0 +false +def f + +1 +Constructor.scala +covtest +C +Class +covtest.C +x +48 +53 +4 +x +DefDef +false +0 +false +def x + +2 +Constructor.scala +covtest +C +Class +covtest.C + +60 +64 +5 +f +Apply +false +0 +false +f(x) + +3 +Constructor.scala +covtest +O$ +Object +covtest.O$ +g +78 +83 +8 +g +DefDef +false +0 +false +def g + +4 +Constructor.scala +covtest +O$ +Object +covtest.O$ +y +98 +103 +9 +y +DefDef +false +0 +false +def y + +5 +Constructor.scala +covtest +O$ +Object +covtest.O$ + +110 +114 +10 +g +Apply +false +0 +false +g(y) + diff --git a/tests/coverage/pos/ContextFunctions.scala b/tests/coverage/pos/ContextFunctions.scala new file mode 100644 index 000000000000..04f5b6768c6c --- /dev/null +++ b/tests/coverage/pos/ContextFunctions.scala @@ -0,0 +1,14 @@ +package covtest + +class OnError(op: Exception => Any): + def onError(fallback: Any): Any = fallback + +class Imperative: + + def readName2 = (t: Exception) ?=> (c: String) ?=> { + "name" + } + + def readPerson(s: String) = + val e: Exception = null + OnError((e) => readName2(using e)(using s)).onError(None) diff --git a/tests/coverage/pos/ContextFunctions.scoverage.check b/tests/coverage/pos/ContextFunctions.scoverage.check new file mode 100644 index 000000000000..74a76df8bbf4 --- /dev/null +++ b/tests/coverage/pos/ContextFunctions.scoverage.check @@ -0,0 +1,190 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +ContextFunctions.scala +covtest +OnError +Class +covtest.OnError +onError +56 +67 +3 +onError +DefDef +false +0 +false +def onError + +1 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readName2 +121 +134 +7 +readName2 +DefDef +false +0 +false +def readName2 + +2 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +apply +Apply +false +0 +false +readName2(using e)(using s) + +3 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 + +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +4 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +invoked +Apply +false +0 +false +readName2(using e)(using s) + +5 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +apply +Apply +false +0 +false +readName2(using e)(using s) + +6 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 +invoked +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +7 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 + +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +8 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +309 +13 +onError +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)).onError(None) + +9 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +192 +206 +11 +readPerson +DefDef +false +0 +false +def readPerson + diff --git a/tests/coverage/pos/Enum.scala b/tests/coverage/pos/Enum.scala new file mode 100644 index 000000000000..b5710432f6b6 --- /dev/null +++ b/tests/coverage/pos/Enum.scala @@ -0,0 +1,41 @@ +package covtest + +/** + * Enum Types: https://dotty.epfl.ch/docs/reference/enums/adts.html + * Taken from https://github.com/scala/scala3-example-project + */ +object EnumTypes: + + enum ListEnum[+A]: + case Cons(h: A, t: ListEnum[A]) + case Empty + + enum Planet(mass: Double, radius: Double): + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24, 6.0518e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + case Mars extends Planet(6.421e+23, 3.3972e6) + case Jupiter extends Planet(1.9e+27, 7.1492e7) + case Saturn extends Planet(5.688e+26, 6.0268e7) + case Uranus extends Planet(8.686e+25, 2.5559e7) + case Neptune extends Planet(1.024e+26, 2.4746e7) + end Planet + + def test(): Unit = + val emptyList = ListEnum.Empty + val list = ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) + println("Example 1: \n"+emptyList) + println(s"${list}\n") + + def calculateEarthWeightOnPlanets(earthWeight: Double) = + val mass = earthWeight/Planet.Earth.surfaceGravity + for p <- Planet.values do + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + + println("Example 2:") + calculateEarthWeightOnPlanets(80) + end test diff --git a/tests/coverage/pos/Enum.scoverage.check b/tests/coverage/pos/Enum.scoverage.check new file mode 100644 index 000000000000..d76c749275e7 --- /dev/null +++ b/tests/coverage/pos/Enum.scoverage.check @@ -0,0 +1,531 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +* +Apply +false +0 +false +G * mass + +1 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +invoked +Apply +false +0 +false +G * mass + +2 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +* +Apply +false +0 +false +G * mass + +3 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +371 +386 +14 +* +Apply +false +0 +false +radius * radius + +4 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +386 +14 +/ +Apply +false +0 +false +G * mass / (radius * radius + +5 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +338 +356 +14 +surfaceGravity +DefDef +false +0 +false +def surfaceGravity + +6 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +432 +458 +15 +* +Apply +false +0 +false +otherMass * surfaceGravity + +7 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +392 +409 +15 +surfaceWeight +DefDef +false +0 +false +def surfaceWeight + +8 +Enum.scala +covtest +$anon +Class +covtest.$anon + +485 +512 +17 + +Apply +false +0 +false +Planet(3.303e+23, 2.4397e6) + +9 +Enum.scala +covtest +$anon +Class +covtest.$anon + +538 +565 +18 + +Apply +false +0 +false +Planet(4.869e+24, 6.0518e6) + +10 +Enum.scala +covtest +$anon +Class +covtest.$anon + +591 +619 +19 + +Apply +false +0 +false +Planet(5.976e+24, 6.37814e6) + +11 +Enum.scala +covtest +$anon +Class +covtest.$anon + +645 +672 +20 + +Apply +false +0 +false +Planet(6.421e+23, 3.3972e6) + +12 +Enum.scala +covtest +$anon +Class +covtest.$anon + +698 +725 +21 + +Apply +false +0 +false +Planet(1.9e+27, 7.1492e7) + +13 +Enum.scala +covtest +$anon +Class +covtest.$anon + +751 +778 +22 + +Apply +false +0 +false +Planet(5.688e+26, 6.0268e7) + +14 +Enum.scala +covtest +$anon +Class +covtest.$anon + +804 +831 +23 + +Apply +false +0 +false +Planet(8.686e+25, 2.5559e7) + +15 +Enum.scala +covtest +$anon +Class +covtest.$anon + +857 +884 +24 + +Apply +false +0 +false +Planet(1.024e+26, 2.4746e7) + +16 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1051 +1076 +30 ++ +Apply +false +0 +false +"Example 1: \n"+emptyList + +17 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1043 +1077 +30 +println +Apply +false +0 +false +println("Example 1: \n"+emptyList) + +18 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1090 +1102 +31 +s +Apply +false +0 +false +s"${list}\n" + +19 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1082 +1103 +31 +println +Apply +false +0 +false +println(s"${list}\n") + +20 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1183 +1222 +34 +/ +Apply +false +0 +false +earthWeight/Planet.Earth.surfaceGravity + +21 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1238 +1251 +35 +refArrayOps +Apply +false +0 +false +Planet.values + +22 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +$anonfun +1296 +1317 +36 +surfaceWeight +Apply +false +0 +false +p.surfaceWeight(mass) + +23 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +$anonfun +1271 +1319 +36 +s +Apply +false +0 +false +s"Your weight on $p is ${p.surfaceWeight(mass)}" + +24 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +$anonfun +1263 +1320 +36 +println +Apply +false +0 +false +println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +25 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1229 +1320 +35 +foreach +Apply +false +0 +false +for p <- Planet.values do + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +26 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1109 +1142 +33 +calculateEarthWeightOnPlanets +DefDef +false +0 +false +def calculateEarthWeightOnPlanets + +27 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1326 +1347 +38 +println +Apply +false +0 +false +println("Example 2:") + +28 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +1352 +1385 +39 +calculateEarthWeightOnPlanets +Apply +false +0 +false +calculateEarthWeightOnPlanets(80) + +29 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +901 +909 +27 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/Givens.scala b/tests/coverage/pos/Givens.scala new file mode 100644 index 000000000000..2f9cbdfd1169 --- /dev/null +++ b/tests/coverage/pos/Givens.scala @@ -0,0 +1,23 @@ +package covtest + +import scala.language.strictEquality + +class Context(val id: Int) + +class Givens: + + def test(): Unit = + given CanEqual[Int, String] = CanEqual.derived + println(3 == "3") + println(3 == 5.1) + + def printContext(msg: String)(using ctx: Context): Unit = + println(msg) + println(ctx.id) + + def getMessage(i: Int): String = i.toString + + def test2() = + given c: Context = Context(0) + printContext("test")(using c) + printContext(getMessage(123)) diff --git a/tests/coverage/pos/Givens.scoverage.check b/tests/coverage/pos/Givens.scoverage.check new file mode 100644 index 000000000000..389713521448 --- /dev/null +++ b/tests/coverage/pos/Givens.scoverage.check @@ -0,0 +1,275 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +182 +190 +10 +== +Apply +false +0 +false +3 == "3" + +1 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +174 +191 +10 +println +Apply +false +0 +false +println(3 == "3") + +2 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +204 +212 +11 +== +Apply +false +0 +false +3 == 5.1 + +3 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +196 +213 +11 +println +Apply +false +0 +false +println(3 == 5.1) + +4 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +100 +108 +8 +test +DefDef +false +0 +false +def test + +5 +Givens.scala +covtest +Givens +Class +covtest.Givens +printContext +279 +291 +14 +println +Apply +false +0 +false +println(msg) + +6 +Givens.scala +covtest +Givens +Class +covtest.Givens +printContext +296 +311 +15 +println +Apply +false +0 +false +println(ctx.id) + +7 +Givens.scala +covtest +Givens +Class +covtest.Givens +printContext +217 +233 +13 +printContext +DefDef +false +0 +false +def printContext + +8 +Givens.scala +covtest +Givens +Class +covtest.Givens +getMessage +348 +358 +17 +toString +Apply +false +0 +false +i.toString + +9 +Givens.scala +covtest +Givens +Class +covtest.Givens +getMessage +315 +329 +17 +getMessage +DefDef +false +0 +false +def getMessage + +10 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +399 +409 +20 + +Apply +false +0 +false +Context(0) + +11 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +414 +443 +21 +printContext +Apply +false +0 +false +printContext("test")(using c) + +12 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +461 +476 +22 +getMessage +Apply +false +0 +false +getMessage(123) + +13 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +448 +477 +22 +printContext +Apply +false +0 +false +printContext(getMessage(123)) + +14 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +362 +371 +19 +test2 +DefDef +false +0 +false +def test2 + diff --git a/tests/coverage/pos/Inlined.scala b/tests/coverage/pos/Inlined.scala new file mode 100644 index 000000000000..ebe0503716a7 --- /dev/null +++ b/tests/coverage/pos/Inlined.scala @@ -0,0 +1,8 @@ +package covtest + +// Checks that we use the new positions of the inlined code properly +def testInlined(): Unit = + val l = 1 + assert(l == 1) // scala.Predef.assert is inline in dotty + assert(l == List(l).length) + assert(List(l).length == 1) diff --git a/tests/coverage/pos/Inlined.scoverage.check b/tests/coverage/pos/Inlined.scoverage.check new file mode 100644 index 000000000000..f3cb3b5d9026 --- /dev/null +++ b/tests/coverage/pos/Inlined.scoverage.check @@ -0,0 +1,275 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +133 +139 +5 +== +Apply +false +0 +false +l == 1 + +1 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false +scala.runtime.Scala3RunTime + +2 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +3 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +4 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +197 +204 +6 +apply +Apply +false +0 +false +List(l) + +5 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +192 +211 +6 +== +Apply +false +0 +false +l == List(l).length + +6 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false +scala.runtime.Scala3RunTime + +7 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +8 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +9 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +222 +229 +7 +apply +Apply +false +0 +false +List(l) + +10 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +222 +241 +7 +== +Apply +false +0 +false +List(l).length == 1 + +11 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false +scala.runtime.Scala3RunTime + +12 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +13 +../../../library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false +scala.runtime.Scala3RunTime.assertFailed() + +14 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +86 +101 +3 +testInlined +DefDef +false +0 +false +def testInlined + diff --git a/tests/coverage/pos/Literals.scala b/tests/coverage/pos/Literals.scala new file mode 100644 index 000000000000..4b61238b2015 --- /dev/null +++ b/tests/coverage/pos/Literals.scala @@ -0,0 +1,12 @@ +package covtest + +def block = + println("not this") // this literal should not be instrumented, only the println call + 12 + true + null + +def f(x: Int, y: Int, z: Int)(t: Int) = ??? + +def main: Unit = + f(0,1,2)(3) diff --git a/tests/coverage/pos/Literals.scoverage.check b/tests/coverage/pos/Literals.scoverage.check new file mode 100644 index 000000000000..bda71a49f3b1 --- /dev/null +++ b/tests/coverage/pos/Literals.scoverage.check @@ -0,0 +1,105 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +block +31 +50 +3 +println +Apply +false +0 +false +println("not this") + +1 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +block +17 +26 +2 +block +DefDef +false +0 +false +def block + +2 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +f +137 +142 +8 +f +DefDef +false +0 +false +def f + +3 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +main +201 +212 +11 +f +Apply +false +0 +false +f(0,1,2)(3) + +4 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +main +182 +190 +10 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/pos/MatchCaseClasses.scala b/tests/coverage/pos/MatchCaseClasses.scala new file mode 100644 index 000000000000..9e94be869a37 --- /dev/null +++ b/tests/coverage/pos/MatchCaseClasses.scala @@ -0,0 +1,15 @@ +package covtest + +case class Pat1(x: Int) +case class Pat2(x: Int, y: Any) + +object MatchCaseClasses: + def f(x: Any): Unit = x match + case Pat1(0) => println("a") + case Pat1(_) => println("b") + case p @ Pat2(1, -1) => println("c") + case Pat2(_, y: String) => + println(y) + println("d") + case p: Pat2 => println("e") + case _ => println("other") diff --git a/tests/coverage/pos/MatchCaseClasses.scoverage.check b/tests/coverage/pos/MatchCaseClasses.scoverage.check new file mode 100644 index 000000000000..0e469fc86b7c --- /dev/null +++ b/tests/coverage/pos/MatchCaseClasses.scoverage.check @@ -0,0 +1,258 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +151 +163 +7 +println +Apply +false +0 +false +println("a") + +1 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +135 +147 +7 + +Block +false +0 +false +case Pat1(0) + +2 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +184 +196 +8 +println +Apply +false +0 +false +println("b") + +3 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +168 +180 +8 + +Block +false +0 +false +case Pat1(_) + +4 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +225 +237 +9 +println +Apply +false +0 +false +println("c") + +5 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +201 +221 +9 + +Block +false +0 +false +case p @ Pat2(1, -1) + +6 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +275 +285 +11 +println +Apply +false +0 +false +println(y) + +7 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +292 +304 +12 +println +Apply +false +0 +false +println("d") + +8 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +242 +265 +10 + +Block +false +0 +false +case Pat2(_, y: String) + +9 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +325 +337 +13 +println +Apply +false +0 +false +println("e") + +10 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +309 +321 +13 + +Block +false +0 +false +case p: Pat2 + +11 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +352 +368 +14 +println +Apply +false +0 +false +println("other") + +12 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +342 +348 +14 + +Block +false +0 +false +case _ + +13 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +101 +106 +6 +f +DefDef +false +0 +false +def f + diff --git a/tests/coverage/pos/MatchNumbers.scala b/tests/coverage/pos/MatchNumbers.scala new file mode 100644 index 000000000000..f160ab883402 --- /dev/null +++ b/tests/coverage/pos/MatchNumbers.scala @@ -0,0 +1,12 @@ +package covtest + +object MatchNumbers: + type Integer = Int | Long + + def f(i: Integer): Int = i match + case x: Int if x < 0 => -1 + case x: Int => x + case y: Long => y.toInt + + f(0) + f(1L) diff --git a/tests/coverage/pos/MatchNumbers.scoverage.check b/tests/coverage/pos/MatchNumbers.scoverage.check new file mode 100644 index 000000000000..f16c11031784 --- /dev/null +++ b/tests/coverage/pos/MatchNumbers.scoverage.check @@ -0,0 +1,139 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +106 +126 +6 + +Block +false +0 +false +case x: Int if x < 0 + +1 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +121 +126 +6 +< +Apply +false +0 +false +x < 0 + +2 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +137 +148 +7 + +Block +false +0 +false +case x: Int + +3 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +158 +170 +8 + +Block +false +0 +false +case y: Long + +4 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +69 +74 +5 +f +DefDef +false +0 +false +def f + +5 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ + +185 +189 +10 +f +Apply +false +0 +false +f(0) + +6 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ + +192 +197 +11 +f +Apply +false +0 +false +f(1L) + diff --git a/tests/coverage/pos/Select.scala b/tests/coverage/pos/Select.scala new file mode 100644 index 000000000000..2c9e63552586 --- /dev/null +++ b/tests/coverage/pos/Select.scala @@ -0,0 +1,20 @@ +package covtest + +trait T: + def print(): Unit + +class A extends T: + def print() = println("A") + def instance = this + +class B extends A: + override def print() = + super.print() + println(this.instance) + +def test(): Unit = + val a = A() + val aNew = new A + + a.instance.print() // should instrument `a.instance` and `(a.instance).print()` + a.print() diff --git a/tests/coverage/pos/Select.scoverage.check b/tests/coverage/pos/Select.scoverage.check new file mode 100644 index 000000000000..ef2831ee1013 --- /dev/null +++ b/tests/coverage/pos/Select.scoverage.check @@ -0,0 +1,224 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +Select.scala +covtest +A +Class +covtest.A +print +82 +94 +6 +println +Apply +false +0 +false +println("A") + +1 +Select.scala +covtest +A +Class +covtest.A +print +68 +77 +6 +print +DefDef +false +0 +false +def print + +2 +Select.scala +covtest +A +Class +covtest.A +instance +97 +109 +7 +instance +DefDef +false +0 +false +def instance + +3 +Select.scala +covtest +B +Class +covtest.B + +134 +135 +9 + +Apply +false +0 +false +A + +4 +Select.scala +covtest +B +Class +covtest.B +print +166 +179 +11 +print +Apply +false +0 +false +super.print() + +5 +Select.scala +covtest +B +Class +covtest.B +print +184 +206 +12 +println +Apply +false +0 +false +println(this.instance) + +6 +Select.scala +covtest +B +Class +covtest.B +print +139 +157 +10 +print +DefDef +false +0 +false +override def print + +7 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +237 +240 +15 + +Apply +false +0 +false +A() + +8 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +254 +259 +16 + +Apply +false +0 +false +new A + +9 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +263 +281 +18 +print +Apply +false +0 +false +a.instance.print() + +10 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +345 +354 +19 +print +Apply +false +0 +false +a.print() + +11 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +208 +216 +14 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/SimpleMethods.scala b/tests/coverage/pos/SimpleMethods.scala new file mode 100644 index 000000000000..510a86799b32 --- /dev/null +++ b/tests/coverage/pos/SimpleMethods.scala @@ -0,0 +1,23 @@ +package covtest + +class C: + def a: C = this + def b: Unit = return + def c: Unit = () + def d: Int = 12 + def e: Null = null + + def block: Int = + "literal" + 0 + + def cond: Boolean = + if false then true + else false + + def new1: C = new {} + + def tryCatch: Unit = + try () + catch + case e: Exception => 1 diff --git a/tests/coverage/pos/SimpleMethods.scoverage.check b/tests/coverage/pos/SimpleMethods.scoverage.check new file mode 100644 index 000000000000..f2d9d61a1cd4 --- /dev/null +++ b/tests/coverage/pos/SimpleMethods.scoverage.check @@ -0,0 +1,241 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +SimpleMethods.scala +covtest +C +Class +covtest.C +a +28 +33 +3 +a +DefDef +false +0 +false +def a + +1 +SimpleMethods.scala +covtest +C +Class +covtest.C +b +46 +51 +4 +b +DefDef +false +0 +false +def b + +2 +SimpleMethods.scala +covtest +C +Class +covtest.C +c +69 +74 +5 +c +DefDef +false +0 +false +def c + +3 +SimpleMethods.scala +covtest +C +Class +covtest.C +d +88 +93 +6 +d +DefDef +false +0 +false +def d + +4 +SimpleMethods.scala +covtest +C +Class +covtest.C +e +106 +111 +7 +e +DefDef +false +0 +false +def e + +5 +SimpleMethods.scala +covtest +C +Class +covtest.C +block +128 +137 +9 +block +DefDef +false +0 +false +def block + +6 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +206 +210 +14 + +Literal +true +0 +false +true + +7 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +220 +225 +15 + +Literal +true +0 +false +false + +8 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +168 +176 +13 +cond +DefDef +false +0 +false +def cond + +9 +SimpleMethods.scala +covtest +C +Class +covtest.C +new1 +229 +237 +17 +new1 +DefDef +false +0 +false +def new1 + +10 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +282 +284 +20 + +Literal +true +0 +false +() + +11 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +301 +318 +22 + +Block +false +0 +false +case e: Exception + +12 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +253 +265 +19 +tryCatch +DefDef +false +0 +false +def tryCatch + diff --git a/tests/coverage/pos/StructuralTypes.scala b/tests/coverage/pos/StructuralTypes.scala new file mode 100644 index 000000000000..f0472345b9d4 --- /dev/null +++ b/tests/coverage/pos/StructuralTypes.scala @@ -0,0 +1,14 @@ +package covtest + +object StructuralTypes: + + case class Record(elems: (String, Any)*) extends Selectable: + def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2 + + type Person = Record { + val name: String + } + + def test(): Unit = + val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person] + person.name diff --git a/tests/coverage/pos/StructuralTypes.scoverage.check b/tests/coverage/pos/StructuralTypes.scoverage.check new file mode 100644 index 000000000000..43ddfd7715b8 --- /dev/null +++ b/tests/coverage/pos/StructuralTypes.scoverage.check @@ -0,0 +1,139 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +StructuralTypes.scala +covtest +Record +Class +covtest.Record +$anonfun +159 +171 +5 +== +Apply +false +0 +false +_._1 == name + +1 +StructuralTypes.scala +covtest +Record +Class +covtest.Record +selectDynamic +148 +172 +5 +find +Apply +false +0 +false +elems.find(_._1 == name) + +2 +StructuralTypes.scala +covtest +Record +Class +covtest.Record +selectDynamic +109 +126 +5 +selectDynamic +DefDef +false +0 +false +def selectDynamic + +3 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +277 +293 +12 +-> +Apply +false +0 +false +"name" -> "Emma" + +4 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +295 +306 +12 +-> +Apply +false +0 +false +"age" -> 42 + +5 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +333 +344 +13 +selectDynamic +Apply +false +0 +false +person.name + +6 +StructuralTypes.scala +covtest +StructuralTypes$ +Object +covtest.StructuralTypes$ +test +234 +242 +11 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/TypeLambdas.scala b/tests/coverage/pos/TypeLambdas.scala new file mode 100644 index 000000000000..9c7641c025e2 --- /dev/null +++ b/tests/coverage/pos/TypeLambdas.scala @@ -0,0 +1,19 @@ +package covtest + +/** + * Type Lambdas: https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html + * Taken from https://github.com/scala/scala3-example-project + */ +object TypeLambdas: + + type M = [X, Y] =>> Map[Y, X] + + type Tuple = [X] =>> (X, X) + + def test(): Unit = + val m: M[String, Int] = Map(1 -> "1") + println(m) + + val tuple: Tuple[String] = ("a", "b") + println(tuple) + diff --git a/tests/coverage/pos/TypeLambdas.scoverage.check b/tests/coverage/pos/TypeLambdas.scoverage.check new file mode 100644 index 000000000000..f97d5285cf06 --- /dev/null +++ b/tests/coverage/pos/TypeLambdas.scoverage.check @@ -0,0 +1,105 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +310 +318 +13 +-> +Apply +false +0 +false +1 -> "1" + +1 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +306 +319 +13 +apply +Apply +false +0 +false +Map(1 -> "1") + +2 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +324 +334 +14 +println +Apply +false +0 +false +println(m) + +3 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +382 +396 +17 +println +Apply +false +0 +false +println(tuple) + +4 +TypeLambdas.scala +covtest +TypeLambdas$ +Object +covtest.TypeLambdas$ +test +259 +267 +12 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/run/currying/test.check b/tests/coverage/run/currying/test.check new file mode 100644 index 000000000000..d21dbd35bc5c --- /dev/null +++ b/tests/coverage/run/currying/test.check @@ -0,0 +1,4 @@ +3 +3 +3 +3 \ No newline at end of file diff --git a/tests/coverage/run/currying/test.scala b/tests/coverage/run/currying/test.scala new file mode 100644 index 000000000000..4776b6bde855 --- /dev/null +++ b/tests/coverage/run/currying/test.scala @@ -0,0 +1,15 @@ +object Test: + def f1(a: Int)(b: Int)(c: Int) = a+b+c + def f2 = (a: Int) => (b: Int) => (c: Int) => + a+b+c + + def g1 = (a: Int) ?=> (b: Int) ?=> (c: Int) ?=> + a+b+c + + def g2(using a: Int)(using b: Int)(using c: Int) = a+b+c + + def main(args: Array[String]): Unit = + println(f1(0)(1)(2)) + println(f2(0)(1)(2)) + println(g1(using 0)(using 1)(using 2)) + println(g2(using 0)(using 1)(using 2)) diff --git a/tests/coverage/run/currying/test.scoverage.check b/tests/coverage/run/currying/test.scoverage.check new file mode 100644 index 000000000000..688c75b42933 --- /dev/null +++ b/tests/coverage/run/currying/test.scoverage.check @@ -0,0 +1,411 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +currying/test.scala + +Test$ +Object +.Test$ +f1 +48 +51 +1 ++ +Apply +false +0 +false +a+b + +1 +currying/test.scala + +Test$ +Object +.Test$ +f1 +48 +53 +1 ++ +Apply +false +0 +false +a+b+c + +2 +currying/test.scala + +Test$ +Object +.Test$ +f1 +15 +21 +1 +f1 +DefDef +false +0 +false +def f1 + +3 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +105 +108 +3 ++ +Apply +false +0 +false +a+b + +4 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +105 +110 +3 ++ +Apply +false +0 +false +a+b+c + +5 +currying/test.scala + +Test$ +Object +.Test$ +f2 +56 +62 +2 +f2 +DefDef +false +0 +false +def f2 + +6 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +166 +169 +6 ++ +Apply +false +0 +false +a+b + +7 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +166 +171 +6 ++ +Apply +false +0 +false +a+b+c + +8 +currying/test.scala + +Test$ +Object +.Test$ +g1 +114 +120 +5 +g1 +DefDef +false +0 +false +def g1 + +9 +currying/test.scala + +Test$ +Object +.Test$ +g2 +226 +229 +8 ++ +Apply +false +0 +false +a+b + +10 +currying/test.scala + +Test$ +Object +.Test$ +g2 +226 +231 +8 ++ +Apply +false +0 +false +a+b+c + +11 +currying/test.scala + +Test$ +Object +.Test$ +g2 +175 +181 +8 +g2 +DefDef +false +0 +false +def g2 + +12 +currying/test.scala + +Test$ +Object +.Test$ +main +285 +296 +11 +f1 +Apply +false +0 +false +f1(0)(1)(2) + +13 +currying/test.scala + +Test$ +Object +.Test$ +main +277 +297 +11 +println +Apply +false +0 +false +println(f1(0)(1)(2)) + +14 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +315 +12 +apply +Apply +false +0 +false +f2(0) + +15 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +318 +12 +apply +Apply +false +0 +false +f2(0)(1) + +16 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +321 +12 +apply +Apply +false +0 +false +f2(0)(1)(2) + +17 +currying/test.scala + +Test$ +Object +.Test$ +main +302 +322 +12 +println +Apply +false +0 +false +println(f2(0)(1)(2)) + +18 +currying/test.scala + +Test$ +Object +.Test$ +main +335 +364 +13 +apply +Apply +false +0 +false +g1(using 0)(using 1)(using 2) + +19 +currying/test.scala + +Test$ +Object +.Test$ +main +327 +365 +13 +println +Apply +false +0 +false +println(g1(using 0)(using 1)(using 2)) + +20 +currying/test.scala + +Test$ +Object +.Test$ +main +378 +407 +14 +g2 +Apply +false +0 +false +g2(using 0)(using 1)(using 2) + +21 +currying/test.scala + +Test$ +Object +.Test$ +main +370 +408 +14 +println +Apply +false +0 +false +println(g2(using 0)(using 1)(using 2)) + +22 +currying/test.scala + +Test$ +Object +.Test$ +main +235 +243 +10 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/inheritance/test.check b/tests/coverage/run/inheritance/test.check new file mode 100644 index 000000000000..59fbc504fb8c --- /dev/null +++ b/tests/coverage/run/inheritance/test.check @@ -0,0 +1,3 @@ +block +1 +2 \ No newline at end of file diff --git a/tests/coverage/run/inheritance/test.scala b/tests/coverage/run/inheritance/test.scala new file mode 100644 index 000000000000..b5be83a378ea --- /dev/null +++ b/tests/coverage/run/inheritance/test.scala @@ -0,0 +1,10 @@ +class A(val x: Int, val y: Int) +class B(x: Int) extends A(x, 0) +class C1 extends B({println("block"); 1}) +class C2 extends B(A(2,2).x) + +@main +def Test: Unit = + println(C1().x) // block + // 1 + println(C2().x) // 2 diff --git a/tests/coverage/run/inheritance/test.scoverage.check b/tests/coverage/run/inheritance/test.scoverage.check new file mode 100644 index 000000000000..5744f4b5eb3b --- /dev/null +++ b/tests/coverage/run/inheritance/test.scoverage.check @@ -0,0 +1,191 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +inheritance/test.scala + +B +Class +.B + +56 +63 +1 + +Apply +false +0 +false +A(x, 0) + +1 +inheritance/test.scala + +C1 +Class +.C1 + +84 +100 +2 +println +Apply +false +0 +false +println("block") + +2 +inheritance/test.scala + +C1 +Class +.C1 + +81 +105 +2 + +Apply +false +0 +false +B({println("block"); 1}) + +3 +inheritance/test.scala + +C2 +Class +.C2 + +125 +131 +3 + +Apply +false +0 +false +A(2,2) + +4 +inheritance/test.scala + +C2 +Class +.C2 + +123 +134 +3 + +Apply +false +0 +false +B(A(2,2).x) + +5 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +169 +173 +7 + +Apply +false +0 +false +C1() + +6 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +161 +176 +7 +println +Apply +false +0 +false +println(C1().x) + +7 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +219 +223 +9 + +Apply +false +0 +false +C2() + +8 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +211 +226 +9 +println +Apply +false +0 +false +println(C2().x) + +9 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +136 +150 +6 +Test +DefDef +false +0 +false +@main +def Test + diff --git a/tests/coverage/run/interpolation/test.check b/tests/coverage/run/interpolation/test.check new file mode 100644 index 000000000000..9ce4b367db49 --- /dev/null +++ b/tests/coverage/run/interpolation/test.check @@ -0,0 +1,5 @@ +0: d +1: o +2: t +3: t +4: y diff --git a/tests/coverage/run/interpolation/test.scala b/tests/coverage/run/interpolation/test.scala new file mode 100644 index 000000000000..3745367b593d --- /dev/null +++ b/tests/coverage/run/interpolation/test.scala @@ -0,0 +1,9 @@ +object Test: + + def simple(a: Int, b: String): String = + s"$a, ${b.length}" + + def main(args: Array[String]): Unit = + val xs: List[String] = List("d", "o", "t", "t", "y") + + xs.zipWithIndex.map((s, i) => println(s"$i: $s")) diff --git a/tests/coverage/run/interpolation/test.scoverage.check b/tests/coverage/run/interpolation/test.scoverage.check new file mode 100644 index 000000000000..9c93d58f182c --- /dev/null +++ b/tests/coverage/run/interpolation/test.scoverage.check @@ -0,0 +1,156 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +68 +76 +3 +length +Apply +false +0 +false +b.length + +1 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +60 +78 +3 +s +Apply +false +0 +false +s"$a, ${b.length}" + +2 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +16 +26 +2 +simple +DefDef +false +0 +false +def simple + +3 +interpolation/test.scala + +Test$ +Object +.Test$ +main +147 +176 +6 +apply +Apply +false +0 +false +List("d", "o", "t", "t", "y") + +4 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +220 +229 +8 +s +Apply +false +0 +false +s"$i: $s" + +5 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +212 +230 +8 +println +Apply +false +0 +false +println(s"$i: $s") + +6 +interpolation/test.scala + +Test$ +Object +.Test$ +main +182 +231 +8 +map +Apply +false +0 +false +xs.zipWithIndex.map((s, i) => println(s"$i: $s")) + +7 +interpolation/test.scala + +Test$ +Object +.Test$ +main +82 +90 +5 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/lifting/test.check b/tests/coverage/run/lifting/test.check new file mode 100644 index 000000000000..fe4a8088d523 --- /dev/null +++ b/tests/coverage/run/lifting/test.check @@ -0,0 +1,3 @@ +string123.0.0 +string123.0.0 +string-1.0.123 diff --git a/tests/coverage/run/lifting/test.scala b/tests/coverage/run/lifting/test.scala new file mode 100644 index 000000000000..2b44168e3aed --- /dev/null +++ b/tests/coverage/run/lifting/test.scala @@ -0,0 +1,20 @@ +class Vals: + val l = List(1) + val ll = l :: List(1,2,3) + +class A: + def msg(a: Int, b: Int, c: Int) = "string" + a + "." + b + "." + c + def integer: Int = 0 + def ex: this.type = this + +@main +def Test: Unit = + val a = A() + val i = 123 + def f() = -1 + var x = a.msg(i, 0, a.integer) + println(x) + x = a.ex.msg(i, 0, a.ex.integer) + println(x) + x = a.msg(f(), 0, i) + println(x) diff --git a/tests/coverage/run/lifting/test.scoverage.check b/tests/coverage/run/lifting/test.scoverage.check new file mode 100644 index 000000000000..7729e7515734 --- /dev/null +++ b/tests/coverage/run/lifting/test.scoverage.check @@ -0,0 +1,378 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +lifting/test.scala + +Vals +Class +.Vals + +22 +29 +1 +apply +Apply +false +0 +false +List(1) + +1 +lifting/test.scala + +Vals +Class +.Vals + +46 +57 +2 +apply +Apply +false +0 +false +List(1,2,3) + +2 +lifting/test.scala + +Vals +Class +.Vals + +41 +57 +2 +:: +Apply +false +0 +false +l :: List(1,2,3) + +3 +lifting/test.scala + +A +Class +.A +msg +104 +116 +5 ++ +Apply +false +0 +false +"string" + a + +4 +lifting/test.scala + +A +Class +.A +msg +104 +122 +5 ++ +Apply +false +0 +false +"string" + a + "." + +5 +lifting/test.scala + +A +Class +.A +msg +104 +126 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + +6 +lifting/test.scala + +A +Class +.A +msg +104 +132 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + "." + +7 +lifting/test.scala + +A +Class +.A +msg +104 +136 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + "." + c + +8 +lifting/test.scala + +A +Class +.A +msg +70 +77 +5 +msg +DefDef +false +0 +false +def msg + +9 +lifting/test.scala + +A +Class +.A +integer +139 +150 +6 +integer +DefDef +false +0 +false +def integer + +10 +lifting/test.scala + +A +Class +.A +ex +162 +168 +7 +ex +DefDef +false +0 +false +def ex + +11 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +221 +224 +11 + +Apply +false +0 +false +A() + +12 +lifting/test.scala + +test$package$ +Object +.test$package$ +f +241 +246 +13 +f +DefDef +false +0 +false +def f + +13 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +264 +286 +14 +msg +Apply +false +0 +false +a.msg(i, 0, a.integer) + +14 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +289 +299 +15 +println +Apply +false +0 +false +println(x) + +15 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +306 +334 +16 +msg +Apply +false +0 +false +a.ex.msg(i, 0, a.ex.integer) + +16 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +337 +347 +17 +println +Apply +false +0 +false +println(x) + +17 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +360 +363 +18 +f +Apply +false +0 +false +f() + +18 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +354 +370 +18 +msg +Apply +false +0 +false +a.msg(f(), 0, i) + +19 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +373 +383 +19 +println +Apply +false +0 +false +println(x) + +20 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +188 +202 +10 +Test +DefDef +false +0 +false +@main +def Test + diff --git a/tests/coverage/run/trait/test.check b/tests/coverage/run/trait/test.check new file mode 100644 index 000000000000..8c9ca6a4ce24 --- /dev/null +++ b/tests/coverage/run/trait/test.check @@ -0,0 +1,3 @@ +0 +test +test diff --git a/tests/coverage/run/trait/test.scala b/tests/coverage/run/trait/test.scala new file mode 100644 index 000000000000..54166d77c17e --- /dev/null +++ b/tests/coverage/run/trait/test.scala @@ -0,0 +1,14 @@ +trait T1: + def x = 0 + +class Impl1 extends T1 + +trait T2(val p: String) +class Impl2 extends T2("test") with T1 +class Impl3 extends T2(Impl2().p) + +@main +def Test: Unit = + println(Impl1().x) // 0 + println(Impl2().p) // test + println(Impl3().p) // test diff --git a/tests/coverage/run/trait/test.scoverage.check b/tests/coverage/run/trait/test.scoverage.check new file mode 100644 index 000000000000..dcc93e2a7809 --- /dev/null +++ b/tests/coverage/run/trait/test.scoverage.check @@ -0,0 +1,208 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +trait/test.scala + +T1 +Trait +.T1 +x +12 +17 +1 +x +DefDef +false +0 +false +def x + +1 +trait/test.scala + +Impl2 +Class +.Impl2 + +91 +101 +6 + +Apply +false +0 +false +T2("test") + +2 +trait/test.scala + +Impl3 +Class +.Impl3 + +133 +140 +7 + +Apply +false +0 +false +Impl2() + +3 +trait/test.scala + +Impl3 +Class +.Impl3 + +130 +143 +7 + +Apply +false +0 +false +T2(Impl2().p) + +4 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +178 +185 +11 + +Apply +false +0 +false +Impl1() + +5 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +170 +188 +11 +println +Apply +false +0 +false +println(Impl1().x) + +6 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +204 +211 +12 + +Apply +false +0 +false +Impl2() + +7 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +196 +214 +12 +println +Apply +false +0 +false +println(Impl2().p) + +8 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +233 +240 +13 + +Apply +false +0 +false +Impl3() + +9 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +225 +243 +13 +println +Apply +false +0 +false +println(Impl3().p) + +10 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +145 +159 +10 +Test +DefDef +false +0 +false +@main +def Test + diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala b/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala deleted file mode 100644 index fb06e93f91c0..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala +++ /dev/null @@ -1,20 +0,0 @@ -import scala.quoted.* - -object Macros: - - inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - - private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - unrolledPowerCode(x, n.valueOrError) - - private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } // tests simple quotes without splices - else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices - else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture - - - inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } - - private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = - // tests use of Type - '{ val y: T = $x; $body(y): U } diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/why.md b/tests/disabled/pos-macros/forwardCompat-3.1/why.md deleted file mode 100644 index f281f9c08662..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.1/why.md +++ /dev/null @@ -1 +0,0 @@ -Disabled until https://github.com/lampepfl/dotty/issues/14306 is fixed diff --git a/tests/explicit-nulls/pos-special/i14947.scala b/tests/explicit-nulls/pos-special/i14947.scala new file mode 100644 index 000000000000..b8f013f325b1 --- /dev/null +++ b/tests/explicit-nulls/pos-special/i14947.scala @@ -0,0 +1,14 @@ +class B: + def g: String | Null = ??? + + def f = + import scala.language.unsafeNulls + if ??? then "" else g + +import scala.language.unsafeNulls +class C { + def g: String | Null = ??? + + def f = + if ??? then "" else g +} diff --git a/tests/neg-custom-args/fatal-warnings/enum-variance.check b/tests/neg-custom-args/fatal-warnings/enum-variance.check index 22b5065f8ea5..b525d4d94ba6 100644 --- a/tests/neg-custom-args/fatal-warnings/enum-variance.check +++ b/tests/neg-custom-args/fatal-warnings/enum-variance.check @@ -1,9 +1,9 @@ -- Error: tests/neg-custom-args/fatal-warnings/enum-variance.scala:2:12 ------------------------------------------------ 2 | case Refl(f: T => T) // error: enum case Refl requires explicit declaration of type T | ^^^^^^^^^ - | contravariant type T occurs in covariant position in type T => T of value f - | enum case Refl requires explicit declaration of type T to resolve this issue. - | See an example at http://dotty.epfl.ch/docs/reference/enums/adts.html#parameter-variance-of-enums + | contravariant type T occurs in covariant position in type T => T of value f + | enum case Refl requires explicit declaration of type T to resolve this issue. + | See an example at https://docs.scala-lang.org/scala3/reference/enums/adts.html#parameter-variance-of-enums -- Error: tests/neg-custom-args/fatal-warnings/enum-variance.scala:5:16 ------------------------------------------------ 5 | case Refl[-T](f: T => T) extends ExplicitView[T] // error: contravariant type T occurs in covariant position | ^^^^^^^^^ diff --git a/tests/neg-custom-args/fatal-warnings/i11097.scala b/tests/neg-custom-args/fatal-warnings/i11097.scala new file mode 100644 index 000000000000..763babff81e2 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i11097.scala @@ -0,0 +1,13 @@ +@main def test: Unit = { + class C { type T1; type T2 } + + def pmatch(s: C): s.T2 = s match { + case p: (C { type T1 = Int; type T2 >: T1 } & s.type) => // error + (3: p.T1): p.T2 + case p: (C { type T1 = String; type T2 >: T1 } & s.type) => // error + ("this branch should be matched": p.T1): p.T2 + } + + // ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String + val x = pmatch(new C { type T1 = String; type T2 = String }) +} \ No newline at end of file diff --git a/tests/neg-macros/i14001/Macro_1.scala b/tests/neg-macros/i14001/Macro_1.scala new file mode 100644 index 000000000000..d1d413b32b37 --- /dev/null +++ b/tests/neg-macros/i14001/Macro_1.scala @@ -0,0 +1,16 @@ +package test +import scala.quoted.* +object Y { + inline def testStuff[T]: T = + ${testStuffImpl} // problem: T inferred to Nothing + // solution add T explicitly + + def testStuffImpl[T: Type](using Quotes): Expr[T] = { + import quotes.reflect.* + + Apply( + Ref(Symbol.requiredMethod("test.A.C.apply")), + List(Literal(IntConstant(1))) + ).asExprOf[T] + } +} diff --git a/tests/neg-macros/i14001/Test_2.scala b/tests/neg-macros/i14001/Test_2.scala new file mode 100644 index 000000000000..a6a613edf8ef --- /dev/null +++ b/tests/neg-macros/i14001/Test_2.scala @@ -0,0 +1,10 @@ +package test +trait B { + case class C(i: Int) +} + +object A extends B + +object X { + val z = Y.testStuff[A.C] // error: Expr cast exception +} diff --git a/tests/neg-scalajs/native-load-spec-need-explicit-name.check b/tests/neg-scalajs/native-load-spec-need-explicit-name.check index 9d91d4897d10..263acca9a2ca 100644 --- a/tests/neg-scalajs/native-load-spec-need-explicit-name.check +++ b/tests/neg-scalajs/native-load-spec-need-explicit-name.check @@ -1,8 +1,48 @@ -- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:6:2 ---------------------------------------------- 6 | @JSGlobal // error | ^^^^^^^^^ - | Native JS members inside non-native objects must have an explicit name in @JSGlobal + | Native JS definitions named 'apply' must have an explicit name in @JSGlobal -- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:10:2 --------------------------------------------- 10 | @JSGlobal // error | ^^^^^^^^^ - | Native JS members inside non-native objects must have an explicit name in @JSGlobal + | Native JS definitions named 'apply' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:14:2 --------------------------------------------- +14 | @JSGlobal // error + | ^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:20:2 --------------------------------------------- +20 | @JSGlobal // error + | ^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:19:2 --------------------------------------------- +19 | @js.native // error + | ^^^^^^^^^^ + | @js.native is not allowed on vars, lazy vals and setter defs +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:24:2 --------------------------------------------- +24 | @JSGlobal // error + | ^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:30:3 --------------------------------------------- +30 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:34:3 --------------------------------------------- +34 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:38:3 --------------------------------------------- +38 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:44:3 --------------------------------------------- +44 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:43:2 --------------------------------------------- +43 | @js.native // error + | ^^^^^^^^^^ + | @js.native is not allowed on vars, lazy vals and setter defs +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:48:3 --------------------------------------------- +48 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSImport diff --git a/tests/neg-scalajs/native-load-spec-need-explicit-name.scala b/tests/neg-scalajs/native-load-spec-need-explicit-name.scala index fc08d414dd63..7235fa4e7a36 100644 --- a/tests/neg-scalajs/native-load-spec-need-explicit-name.scala +++ b/tests/neg-scalajs/native-load-spec-need-explicit-name.scala @@ -1,14 +1,52 @@ import scala.scalajs.js import scala.scalajs.js.annotation.* -object A { +object A1 { @js.native @JSGlobal // error - class B extends js.Object + class apply extends js.Object @js.native @JSGlobal // error - object C extends js.Object + object apply extends js.Object + + @js.native + @JSGlobal // error + class foo_= extends js.Object +} + +object A2 { + @js.native // error + @JSGlobal // error + def foo_=(x: Int): Unit = js.native + + @js.native + @JSGlobal // error + def apply(x: Int): Int = js.native +} + +object B1 { + @js.native + @JSImport("bar.js") // error + class apply extends js.Object + + @js.native + @JSImport("bar.js") // error + object apply extends js.Object + + @js.native + @JSImport("bar.js") // error + class foo_= extends js.Object +} + +object B2 { + @js.native // error + @JSImport("bar.js") // error + def foo_=(x: Int): Unit = js.native + + @js.native + @JSImport("bar.js") // error + def apply(x: Int): Int = js.native } // scala-js#2401 diff --git a/tests/neg/i11170.check b/tests/neg/i11170.check new file mode 100644 index 000000000000..dc54df923e74 --- /dev/null +++ b/tests/neg/i11170.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i11170.scala:7:15 ---------------------------------------------------------------------------------- +7 |abstract class A extends U[String] // error + | ^ + | Member method foo of mixin trait U is missing a concrete super implementation in class A. diff --git a/tests/neg/i11170.scala b/tests/neg/i11170.scala new file mode 100644 index 000000000000..25a0a4df3926 --- /dev/null +++ b/tests/neg/i11170.scala @@ -0,0 +1,8 @@ +trait T[X] { + def foo(x: X): X +} +trait U[X] extends T[X] { + abstract override def foo(x: X): X = super.foo(x) +} +abstract class A extends U[String] // error + diff --git a/tests/neg/i11170a.scala b/tests/neg/i11170a.scala new file mode 100644 index 000000000000..5268c506f33f --- /dev/null +++ b/tests/neg/i11170a.scala @@ -0,0 +1,28 @@ +package apackage { + + abstract class A { + protected val x: Int + } + abstract class A2 { + val x: Int + } +} + +package bpackage { + import apackage._ + + trait B extends A { + println(x) + } + trait B2 extends A2 { + println(x) + } +} + +package cpackage { + import apackage._ + import bpackage._ + + case class C(override protected val x: Int) extends A with B // error + case class C2(override val x: Int) extends A2 with B2 +} \ No newline at end of file diff --git a/tests/neg/i12344.scala b/tests/neg/i12344.scala index 88daf535e699..b2fd6d27c37c 100644 --- a/tests/neg/i12344.scala +++ b/tests/neg/i12344.scala @@ -2,15 +2,15 @@ import scala.quoted.* class C(using q: Quotes)(i: Int = 1, f: q.reflect.Flags = q.reflect.Flags.EmptyFlags) -def test1a(using q: Quotes) = new C() // error -def test2a(using q: Quotes) = new C(1) // error -def test3a(using q: Quotes) = new C(1, q.reflect.Flags.Lazy) // error -def test4a(using q: Quotes) = new C(f = q.reflect.Flags.Lazy) // error +def test1a(using q: Quotes) = new C() +def test2a(using q: Quotes) = new C(1) +def test3a(using q: Quotes) = new C(1, q.reflect.Flags.Lazy) +def test4a(using q: Quotes) = new C(f = q.reflect.Flags.Lazy) -def test1b(using q: Quotes) = C() // error -def test2b(using q: Quotes) = C(1) // error -def test3b(using q: Quotes) = C(1, q.reflect.Flags.Lazy) // error -def test4b(using q: Quotes) = C(f = q.reflect.Flags.Lazy) // error +def test1b(using q: Quotes) = C() +def test2b(using q: Quotes) = C(1) +def test3b(using q: Quotes) = C(1, q.reflect.Flags.Lazy) +def test4b(using q: Quotes) = C(f = q.reflect.Flags.Lazy) def test1c(using q: Quotes) = new C(using q)() def test2c(using q: Quotes) = new C(using q)(1) @@ -22,5 +22,5 @@ def test2d(using q: Quotes) = C(using q)(1) def test3d(using q: Quotes) = C(using q)(1, q.reflect.Flags.Lazy) def test4d(using q: Quotes) = C(using q)(f = q.reflect.Flags.Lazy) -def test1e(using q: Quotes) = new C()() -def test2e(using q: Quotes) = C()() +def test1e(using q: Quotes) = new C()() // error +def test2e(using q: Quotes) = C()() // error diff --git a/tests/neg/i12457.scala b/tests/neg/i12457.scala index 8ebc061d6d59..050597fb341d 100644 --- a/tests/neg/i12457.scala +++ b/tests/neg/i12457.scala @@ -1,3 +1,3 @@ -import language.`3.1-migration` +import language.`3.1` trait X [ X <: Z , Z >: X [ R ] ] // error diff --git a/tests/neg/i12554a.scala b/tests/neg/i12554a.scala new file mode 100644 index 000000000000..3ec6725d5fa4 --- /dev/null +++ b/tests/neg/i12554a.scala @@ -0,0 +1,32 @@ +object Test { + def f(s: String) = s + + def g: (Int, Int) = { + f("Foo") + (1, 2) // error, ok in Scala 2 + } + def g2: (Int, Int) = { + f("Foo") + (1, 2) // ok, ok in Scala 2 + } + + def h: Unit = { + f("Foo") + {} // error, error in Scala 2 + } + + def i: Unit = { + f("Foo") + {} // ok, error in Scala 2 + } + + def j: Int = { + return // error, error in Scala 2 + 1 + 2 + } + + def k: Int = { + return // ok, error in Scala 2 + 1 + 2 + } +} diff --git a/tests/neg/i12554b.scala b/tests/neg/i12554b.scala new file mode 100644 index 000000000000..61c841c799dd --- /dev/null +++ b/tests/neg/i12554b.scala @@ -0,0 +1,30 @@ +import language.`3.0-migration` // behavior should be the same as for Scala-2 +object Test { + + def f(s: String) = s + + def g: (Int, Int) = { + f("Foo") + (1, 2) // ok + } + + def h: Unit = { + f("Foo") + {} // error + } + + def i: Unit = { + f("Foo") + {} // error + } + + def j: Int = { + return // error + 1 + 2 + } + + def k: Int = { + return // error + 1 + 2 + } +} \ No newline at end of file diff --git a/tests/neg/i14966.check b/tests/neg/i14966.check new file mode 100644 index 000000000000..c5227b352b1b --- /dev/null +++ b/tests/neg/i14966.check @@ -0,0 +1,10 @@ +-- Error: tests/neg/i14966.scala:10:9 ---------------------------------------------------------------------------------- +10 | export s.* // error + | ^^^^^^^^^^ + | Clashing exports: The exported + | method f: (x: List[Int]): Int + | and method f²: (x: List[String]): Int + | have the same signature after erasure and overloading resolution could not disambiguate. + | + | where: f is a method in trait S + | f² is a method in trait I diff --git a/tests/neg/i14966.scala b/tests/neg/i14966.scala new file mode 100644 index 000000000000..38c6d99247ea --- /dev/null +++ b/tests/neg/i14966.scala @@ -0,0 +1,11 @@ +trait I[A]: + def f(x: List[String]): A + +trait S: + def f(x: List[Int]): Int + +trait T[A] extends I[A], S + +class Test(s: T[Int]): + export s.* // error + diff --git a/tests/neg/i14966a.check b/tests/neg/i14966a.check new file mode 100644 index 000000000000..777d1ec74955 --- /dev/null +++ b/tests/neg/i14966a.check @@ -0,0 +1,10 @@ +-- [E120] Naming Error: tests/neg/i14966a.scala:3:6 -------------------------------------------------------------------- +3 | def f(x: List[Int]): String = ??? // error + | ^ + | Double definition: + | def f[X <: String](x: List[X]): String in class Test at line 2 and + | def f(x: List[Int]): String in class Test at line 3 + | have the same type (x: scala.collection.immutable.List): String after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. diff --git a/tests/neg/i14966a.scala b/tests/neg/i14966a.scala new file mode 100644 index 000000000000..f2643798f1e5 --- /dev/null +++ b/tests/neg/i14966a.scala @@ -0,0 +1,4 @@ +class Test: + def f[X <: String](x: List[X]): String = ??? + def f(x: List[Int]): String = ??? // error + diff --git a/tests/neg/main-annotation-mainannotation.scala b/tests/neg/main-annotation-mainannotation.scala new file mode 100644 index 000000000000..21e37d1779af --- /dev/null +++ b/tests/neg/main-annotation-mainannotation.scala @@ -0,0 +1,3 @@ +import scala.annotation.MainAnnotation + +@MainAnnotation def f(i: Int, n: Int) = () // error diff --git a/tests/neg/source-import-3-1-migration.check b/tests/neg/source-import-3-1-migration.check new file mode 100644 index 000000000000..d88e72a6871e --- /dev/null +++ b/tests/neg/source-import-3-1-migration.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/source-import-3-1-migration.scala:1:16 ------------------------------------------------------------- +1 |import language.`3.1-migration` // error + | ^^^^^^^^^^^^^^^ + | `3.1-migration` is not a valid source version, did you mean language.`3.1`? diff --git a/tests/neg/source-import-3-1-migration.scala b/tests/neg/source-import-3-1-migration.scala new file mode 100644 index 000000000000..0e84eaea26ea --- /dev/null +++ b/tests/neg/source-import-3-1-migration.scala @@ -0,0 +1 @@ +import language.`3.1-migration` // error diff --git a/tests/pos-macros/InlinedTypeOf.scala b/tests/pos-macros/InlinedTypeOf.scala new file mode 100644 index 000000000000..cd708eb02322 --- /dev/null +++ b/tests/pos-macros/InlinedTypeOf.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +class Sm[T](t: T) + +object Foo { + + inline def foo[T] = { compiletime.summonInline[Type[T]]; ??? } + + def toexpr[T: Type](using Quotes) = foo[Sm[T]] + +} diff --git a/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala b/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala index fb06e93f91c0..868dae764e23 100644 --- a/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala +++ b/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + // Does not compile on 3.0 + // inline def poly: Int = ${ polyCode } + // private def polyCode(using Quotes): Expr[Int] = + // def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + // '{ + // def f[T](x: T): T = ${ bar('x) } + // f[Int](1) + // } diff --git a/tests/pos-macros/backwardCompat-3.0/Test_2.scala b/tests/pos-macros/backwardCompat-3.0/Test_2.scala index 8c0a8004b9cf..d88e018068c8 100644 --- a/tests/pos-macros/backwardCompat-3.0/Test_2.scala +++ b/tests/pos-macros/backwardCompat-3.0/Test_2.scala @@ -13,3 +13,6 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +// Does not compile on 3.0 +// def polyTest: Unit = poly diff --git a/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala b/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala index fb06e93f91c0..9bce5bb93e46 100644 --- a/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala +++ b/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + // Does not compile on 3.1 + // inline def poly: Int = ${ polyCode } + // private def polyCode(using Quotes): Expr[Int] = + // def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + // '{ + // def f[T](x: T): T = ${ bar('x) } + // f[Int](1) + // } diff --git a/tests/pos-macros/backwardCompat-3.1/Test_2.scala b/tests/pos-macros/backwardCompat-3.1/Test_2.scala index 8c0a8004b9cf..b7613398981f 100644 --- a/tests/pos-macros/backwardCompat-3.1/Test_2.scala +++ b/tests/pos-macros/backwardCompat-3.1/Test_2.scala @@ -13,3 +13,6 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +// Does not compile on 3.1 +// def polyTest: Unit = poly diff --git a/tests/pos-macros/baseCompat/Macro_1.scala b/tests/pos-macros/baseCompat/Macro_1.scala index fb06e93f91c0..9dcf082ba023 100644 --- a/tests/pos-macros/baseCompat/Macro_1.scala +++ b/tests/pos-macros/baseCompat/Macro_1.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + inline def poly: Int = ${ polyCode } + + private def polyCode(using Quotes): Expr[Int] = + def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + '{ + def f[T](x: T): T = ${ bar('x) } + f[Int](1) + } diff --git a/tests/pos-macros/baseCompat/Test_2.scala b/tests/pos-macros/baseCompat/Test_2.scala index 8c0a8004b9cf..5bfed5960b3e 100644 --- a/tests/pos-macros/baseCompat/Test_2.scala +++ b/tests/pos-macros/baseCompat/Test_2.scala @@ -13,3 +13,5 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +def polyTest: Unit = poly diff --git a/tests/pos-macros/captured-quoted-def-1/Macro_1.scala b/tests/pos-macros/captured-quoted-def-1/Macro_1.scala new file mode 100644 index 000000000000..fadde58dd168 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-1/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y: Int = 1; ${ identity('y) } } diff --git a/tests/pos-macros/captured-quoted-def-1/Test_2.scala b/tests/pos-macros/captured-quoted-def-1/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-1/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-2/Macro_1.scala b/tests/pos-macros/captured-quoted-def-2/Macro_1.scala new file mode 100644 index 000000000000..3d090311bca1 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-2/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(): Int = 1; ${ identity('{ y() }) } } diff --git a/tests/pos-macros/captured-quoted-def-2/Test_2.scala b/tests/pos-macros/captured-quoted-def-2/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-2/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-3/Macro_1.scala b/tests/pos-macros/captured-quoted-def-3/Macro_1.scala new file mode 100644 index 000000000000..c3ccfdda56a4 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-3/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(i: Int): Int = 1; ${ identity('{ y(1) }) } } diff --git a/tests/pos-macros/captured-quoted-def-3/Test_2.scala b/tests/pos-macros/captured-quoted-def-3/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-3/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-4/Macro_1.scala b/tests/pos-macros/captured-quoted-def-4/Macro_1.scala new file mode 100644 index 000000000000..e88084be84b3 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-4/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(i: Int)(j: Int, k: Int): Int = 1; ${ identity('{ y(1)(2, 3) }) } } diff --git a/tests/pos-macros/captured-quoted-def-4/Test_2.scala b/tests/pos-macros/captured-quoted-def-4/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-4/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-5/Macro_1.scala b/tests/pos-macros/captured-quoted-def-5/Macro_1.scala new file mode 100644 index 000000000000..b047ec26b621 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-5/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y[T](i: T): T = i; ${ identity('{ y[Int](1) }) } } diff --git a/tests/pos-macros/captured-quoted-def-5/Test_2.scala b/tests/pos-macros/captured-quoted-def-5/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-5/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala b/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala new file mode 100644 index 000000000000..533125bdef90 --- /dev/null +++ b/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr('{1}) } + +def myMacroExpr(x: Expr[Int])(using Quotes): Expr[Any] = + '{ + def f(using q1: Quotes) = '{ 1 + ${Expr($x)} } + () + } diff --git a/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala b/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-type-1/Macro_1.scala b/tests/pos-macros/captured-quoted-type-1/Macro_1.scala new file mode 100644 index 000000000000..fdea3ecf4f7f --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-1/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr } + +def myMacroExpr(using Quotes): Expr[Any] = + '{ + def f[Z] = + ${ identity('{ val y: Z = ??? }) } + 42 + } diff --git a/tests/pos-macros/captured-quoted-type-1/Test_2.scala b/tests/pos-macros/captured-quoted-type-1/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-1/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-type-2/Macro_1.scala b/tests/pos-macros/captured-quoted-type-2/Macro_1.scala new file mode 100644 index 000000000000..b9cf18de7ac4 --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-2/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr } + +def myMacroExpr[T: Type](using Quotes): Expr[Any] = + '{ + def f[Z] = + ${ identity('{ val y: (T, Z) = ??? }) } + 42 + } diff --git a/tests/pos-macros/captured-quoted-type-2/Test_2.scala b/tests/pos-macros/captured-quoted-type-2/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-2/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala b/tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala deleted file mode 100644 index fb06e93f91c0..000000000000 --- a/tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala +++ /dev/null @@ -1,20 +0,0 @@ -import scala.quoted.* - -object Macros: - - inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - - private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - unrolledPowerCode(x, n.valueOrError) - - private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } // tests simple quotes without splices - else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices - else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture - - - inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } - - private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = - // tests use of Type - '{ val y: T = $x; $body(y): U } diff --git a/tests/pos-macros/i12440.scala b/tests/pos-macros/i12440.scala new file mode 100644 index 000000000000..4b4c56fef568 --- /dev/null +++ b/tests/pos-macros/i12440.scala @@ -0,0 +1,21 @@ +import scala.quoted.* + +trait Mirror: + type ElemTypes <: Tuple + +class Eq: + + def test1(using Quotes): Unit = '{ + val m: Mirror = ??? + ${ summonType[m.ElemTypes]; ??? } + ${ summonType[List[m.ElemTypes]]; ??? } + } + + def test2(using Quotes): Unit = '{ + val m: Mirror = ??? + type ET = m.ElemTypes + ${ summonType[ET]; ??? } + ${ summonType[List[ET]]; ??? } + } + + def summonType[X](using Type[X]) = ??? diff --git a/tests/pos-macros/i13563.scala b/tests/pos-macros/i13563.scala new file mode 100644 index 000000000000..91a36f447d4a --- /dev/null +++ b/tests/pos-macros/i13563.scala @@ -0,0 +1,3 @@ +import scala.quoted.* +def foo(using Quotes): Unit = + '{ def bar[T](): Unit = ${ summon[Type[T]]; ??? }; () } diff --git a/tests/pos-macros/i13732.scala b/tests/pos-macros/i13732.scala new file mode 100644 index 000000000000..88d987817ca7 --- /dev/null +++ b/tests/pos-macros/i13732.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +def generateImpl(using q: Quotes): Expr[Unit] = + '{ def runEffect[T]: T = ${ runEffectImpl[T] } } + +inline def runEffectImpl[T: Type]: Expr[T] = ??? diff --git a/tests/pos-macros/i14740/Macro_1.scala b/tests/pos-macros/i14740/Macro_1.scala new file mode 100644 index 000000000000..49b869e2b307 --- /dev/null +++ b/tests/pos-macros/i14740/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +type Foo[T] + +transparent inline def emptyList[T]: Any = ${ impl[T] } + +private def impl[T: Type](using Quotes): Expr[Any] = { + import quotes.reflect._ + val tpe = AppliedType(TypeRepr.of[Foo], List(TypeRepr.of[T])) // test AppliedType constructor + Typed('{???}.asTerm, Inferred(tpe)).asExpr // '{ ??? : Foo[T] } +} diff --git a/tests/pos-macros/i14740/Test_2.scala b/tests/pos-macros/i14740/Test_2.scala new file mode 100644 index 000000000000..a59d01526f4f --- /dev/null +++ b/tests/pos-macros/i14740/Test_2.scala @@ -0,0 +1,3 @@ +def test1: Foo[Any] = emptyList[Any] +def test2: Foo[Int] = emptyList[Int] +def test3: Foo[String] = emptyList[String] diff --git a/tests/pos-macros/i4774f.scala b/tests/pos-macros/i4774f.scala index 59d21eb2d2d4..336f20e19280 100644 --- a/tests/pos-macros/i4774f.scala +++ b/tests/pos-macros/i4774f.scala @@ -7,4 +7,10 @@ object Test { def loop2[T](x: Expr[T])(implicit t: Type[T], qctx: Quotes): Expr[T] = '{ def y(): T = $x; ${ loop2('{y()}) } } + + def loop3[T](x: Expr[T])(using Type[T], Quotes): Expr[T] = + '{ def y(i: Int): T = $x; ${ loop2('{y(1)}) } } + + def loop4[T](x: Expr[T])(using Type[T], Quotes): Expr[T] = + '{ def y(i: Int)(j: Int): T = $x; ${ loop2('{y(1)(2)}) } } } diff --git a/tests/pos-macros/i8100.scala b/tests/pos-macros/i8100.scala index 0cf80fe920ee..1a6b5f3d5ba6 100644 --- a/tests/pos-macros/i8100.scala +++ b/tests/pos-macros/i8100.scala @@ -14,8 +14,8 @@ def f[T: Type](using Quotes) = ${ g[m.E](using Type.of[ME]) } ${ g[ME](using Type.of[m.E]) } ${ g[m.E](using Type.of[m.E]) } - // ${ g[ME] } // FIXME: issue seems to be in PickleQuotes - // ${ g[m.E] } // FIXME: issue seems to be in PickleQuotes + ${ g[ME] } + ${ g[m.E] } } def g[T](using Type[T]) = ??? diff --git a/tests/pos-macros/typetags.scala b/tests/pos-macros/typetags.scala index 3a6e60c2e91c..2cf285888272 100644 --- a/tests/pos-macros/typetags.scala +++ b/tests/pos-macros/typetags.scala @@ -7,5 +7,9 @@ object Test { implicitly[Type[List[Int]]] implicitly[Type[T]] implicitly[Type[List[T]]] + Type.of[Int] + Type.of[List[Int]] + Type.of[T] + Type.of[List[T]] } } diff --git a/tests/pos/13491.scala b/tests/pos/13491.scala index b2c7941e15a0..d764494c6048 100644 --- a/tests/pos/13491.scala +++ b/tests/pos/13491.scala @@ -87,7 +87,7 @@ object Rule { def rule[I <: HList, O <: HList](r: Rule[I, O]): Rule[I, O] = ??? - implicit def valueMap[T, Out0 <: HList](m: Map[String, T])(implicit h: HListable[T] { type Out = Out0 }): RuleN[Out0] = ??? + implicit def valueMap[T, Out0 <: HList](m: Map[String, T])(implicit h: HListable[T]): RuleN[h.Out] = ??? } object Test { diff --git a/tests/pos/MathSpec.scala b/tests/pos/MathSpec.scala new file mode 100644 index 000000000000..7c0fdd8a1f3a --- /dev/null +++ b/tests/pos/MathSpec.scala @@ -0,0 +1,21 @@ +trait MathSig { + def sqrt(x: Double): Double +} + +trait MathSpec extends MathSig { + val epsilon = 0.00001 + abstract override def sqrt(x: Double) = { + require(x >= 0) + super.sqrt(x) + } ensuring { result => + (x * x - result).abs < epsilon + } +} + +trait MathImpl extends MathSig { + def sqrt(x: Double): Double = + ??? +} + +object Math extends MathImpl + with MathSpec \ No newline at end of file diff --git a/tests/pos/given-constrapps.scala b/tests/pos/given-constrapps.scala index f92653542861..cdea7967f15e 100644 --- a/tests/pos/given-constrapps.scala +++ b/tests/pos/given-constrapps.scala @@ -19,7 +19,6 @@ class Foo(using TC) { object Test extends App { new C(using tc) - new C()(using tc) new C(using tc) {} new C2(1)(using tc)(using List(tc)) new C2(1)(using tc)(using List(tc)) {} diff --git a/tests/pos/i11712.scala b/tests/pos/i11712.scala new file mode 100644 index 000000000000..27fc1ef2771e --- /dev/null +++ b/tests/pos/i11712.scala @@ -0,0 +1,21 @@ +object Test: + + def transparent = println("transparent method called") + + transparent + println() + inline def f1 = 1 + + transparent + inline def f2 = 2 + + transparent + trait T1 + + transparent + + inline def f3 = 3 + + transparent + + trait T2 \ No newline at end of file diff --git a/tests/pos/i13346.scala b/tests/pos/i13346.scala new file mode 100644 index 000000000000..25702cfe3082 --- /dev/null +++ b/tests/pos/i13346.scala @@ -0,0 +1,19 @@ +object Outer_Typed { + object Inner { + class ClassUntyped(x: Int) + class ClassTyped[T](x: T) // notice type parameter + + // ok + val _ = new ClassUntyped(42) + val _ = new ClassTyped("42") + val _ = ClassUntyped(42) + val _ = ClassTyped("42") + } + + export Inner._ + + val _ = new ClassUntyped(42) + val _ = new ClassTyped("42") + val _ = ClassUntyped(42) + val _ = ClassTyped("42") // error: Not found: ClassTyped +} diff --git a/tests/pos/i14821.scala b/tests/pos/i14821.scala new file mode 100644 index 000000000000..9c007de53322 --- /dev/null +++ b/tests/pos/i14821.scala @@ -0,0 +1,29 @@ +trait Statement +trait Definition extends Statement + +trait ClassDef extends Definition: + def constructor: DefDef + +object ClassDef: + def copy(constr: DefDef): ClassDef = ??? + +// >>> This abstract implementation of DefDef causes a compilation error in transform... +type DefDef <: Definition +val DefDef: DefDefModule = ??? +trait DefDefModule: + def unapply(ddef: DefDef): (String, List[AnyRef]) +// ...unless this given TypeTest is commented out, in which case we get only a type test warning +given scala.reflect.TypeTest[Statement, DefDef] = ??? + +// >>> This alternative works +// trait DefDef extends Definition +// object DefDef: +// def unapply(ddef: DefDef): (String, List[AnyRef]) = ??? + +// >>> This alternative also works +// case class DefDef(name: String, paramss: List[AnyRef]) extends Definition + +def transform(tree: Statement): Statement = tree match + case tree: ClassDef => + val constructor @ DefDef(_, _) = transform(tree.constructor): @unchecked + ClassDef.copy(constructor) diff --git a/tests/pos/i14858/A_2.scala b/tests/pos/i14858/A_2.scala new file mode 100644 index 000000000000..1335f4795508 --- /dev/null +++ b/tests/pos/i14858/A_2.scala @@ -0,0 +1,3 @@ +import p.* + +type NAME2 = C[?] \ No newline at end of file diff --git a/tests/pos/i14858/M_1.scala b/tests/pos/i14858/M_1.scala new file mode 100644 index 000000000000..6f431d0b523e --- /dev/null +++ b/tests/pos/i14858/M_1.scala @@ -0,0 +1,10 @@ +package p + +object M { + class C[N]() +} + +export M.* + +type CC[N] = M.C[N] +type CCC = M.C[Int] diff --git a/tests/pos/i14896.scala b/tests/pos/i14896.scala new file mode 100644 index 000000000000..2ef595fbcaa4 --- /dev/null +++ b/tests/pos/i14896.scala @@ -0,0 +1,2 @@ +object Ex { def unapply(p: Any): Option[_ <: Int] = null } +object Foo { val Ex(_) = null: @unchecked } \ No newline at end of file diff --git a/tests/pos/i14903a.scala b/tests/pos/i14903a.scala new file mode 100644 index 000000000000..9e4e28320987 --- /dev/null +++ b/tests/pos/i14903a.scala @@ -0,0 +1,22 @@ +trait Wrapper[T] { + type Out + } + + type Func[T] = + T match { + case String => Long + case Long => Int + case Int => Float + case Float => Double + case Double => Unit + case Unit => String + } + + implicit def infer[A]: Wrapper[One[A]] { type Out = Func[A] } = ??? + + trait One[A] { + def use(implicit w: Wrapper[One[A]]): One[w.Out] + } + + val x: One[Long] = null + val _ = x.use.use.use.use.use.use.use diff --git a/tests/pos/i14903b.scala b/tests/pos/i14903b.scala new file mode 100644 index 000000000000..cd61a12e858c --- /dev/null +++ b/tests/pos/i14903b.scala @@ -0,0 +1,54 @@ +import annotation.unchecked.uncheckedVariance + +sealed trait HList +sealed trait HNil extends HList +case object HNil extends HNil +case class ::[+H, +T <: HList](head: H, tail: T) extends HList + +type Concat[X <: HList, Y <: HList] <: HList = X match + case HNil => Y + case h :: t => h :: Concat[t, Y] + +/** + * Decompose L into Prefix ++ Suffix if possible +*/ +type StripSuffix[L <: HList, Suffix <: HList] <: Option[HList] = L match + case Suffix => Some[HNil] + case h :: t => StripSuffix[t, Suffix] match + case Some[x] => Some[h :: x] + case _ => None.type + case _ => None.type + +/** + * type-level implementation of this logic: + * Out = + * R if T has a tail of type L + * (L dropRight T) ++ R if L has a tail of type T +*/ +sealed trait TailSwitch[L <: HList, T <: HList, R <: HList]: + type Out <: HList + +object TailSwitch: + type TS[L <: HList, T <: HList, R <: HList] <: HList = + StripSuffix[T, L] match + case Some[_] => R + case _ => StripSuffix[L, T] match + case Some[x] => Concat[x, R] + + implicit def tailSwitch[L <: HList, T <: HList, R <: HList]: (TailSwitch[L, T, R] { + type Out = TS[L, T, R] + }) = new TailSwitch[L, T, R] { type Out = TS[L, T, R] } + +/** + * Rule popping I from stack and pushing back O +*/ +sealed class Rule[-I <: HList, +O <: HList]: + def ~[I2 <: HList, O2 <: HList](that: Rule[I2, O2])(implicit + i: TailSwitch[I2, O @uncheckedVariance, I @uncheckedVariance], + o: TailSwitch[O @uncheckedVariance, I2, O2] + ): Rule[i.Out, o.Out] = ??? + +object Test: + def dot = new Rule[HNil, HNil] {} + def num = new Rule[HNil, Byte :: HNil] {} + def pattern = num ~ dot ~ num ~ dot ~ num ~ dot ~ num // error diff --git a/tests/pos/i14914.scala b/tests/pos/i14914.scala new file mode 100644 index 000000000000..3208a5cf8a02 --- /dev/null +++ b/tests/pos/i14914.scala @@ -0,0 +1,8 @@ + +def Test(b: Boolean) = + val a = + if b then + 1 + else if !b then + 2 + val _: Unit = a diff --git a/tests/pos/i14932.scala b/tests/pos/i14932.scala new file mode 100644 index 000000000000..8fff627fc3d7 --- /dev/null +++ b/tests/pos/i14932.scala @@ -0,0 +1,9 @@ +trait Core { + class Base[T]() +} + +class Module(val core: Core) { + object Indirection { + class Extension[T]() extends core.Base[T]() + } +} \ No newline at end of file diff --git a/tests/pos/i14953.scala b/tests/pos/i14953.scala new file mode 100644 index 000000000000..6448f74cd4ab --- /dev/null +++ b/tests/pos/i14953.scala @@ -0,0 +1,8 @@ +object Test{ + + val xs: Seq[String] = List("Apple", "Orange", "Pear") + export xs.* + +} + +val _ = Test.head diff --git a/tests/pos/i14966.scala b/tests/pos/i14966.scala new file mode 100644 index 000000000000..9ed0e6ce3720 --- /dev/null +++ b/tests/pos/i14966.scala @@ -0,0 +1,13 @@ +trait I[+A] extends IOps[A, I[A]] + +trait S[A] extends I[A], SOps[A, S[A]] + +trait IOps[+A, +C <: I[A]]: + def concat[B >: A](other: IterableOnce[B]): C + +trait SOps[A, +C <: S[A]] extends IOps[A, C]: + def concat(other: IterableOnce[A]): C + +class Test(s: S[Int]): + export s.* + diff --git a/tests/pos/i14966a.scala b/tests/pos/i14966a.scala new file mode 100644 index 000000000000..ad426fc721b2 --- /dev/null +++ b/tests/pos/i14966a.scala @@ -0,0 +1,2 @@ +class B[T](val s: Set[T]): + export s.* diff --git a/tests/pos/i2576.scala b/tests/pos/i2576.scala new file mode 100644 index 000000000000..ac8cdefb1b30 --- /dev/null +++ b/tests/pos/i2576.scala @@ -0,0 +1,3 @@ +class Bar(using x: Int)(y: String) +given Int = ??? +def test = new Bar("") diff --git a/tests/pos/source-import-3-1-migration.scala b/tests/pos/source-import-3-1-migration.scala deleted file mode 100644 index 6e6f9f905b99..000000000000 --- a/tests/pos/source-import-3-1-migration.scala +++ /dev/null @@ -1 +0,0 @@ -import language.`3.1-migration` diff --git a/tests/rewrites/filtering-fors.check b/tests/rewrites/filtering-fors.check new file mode 100644 index 000000000000..f6cac26d8629 --- /dev/null +++ b/tests/rewrites/filtering-fors.check @@ -0,0 +1,6 @@ +val xs: List[Any] = ??? +val as = for case (x: String) <- xs yield x +val bs = + for + case (x: String) <- xs + yield x diff --git a/tests/rewrites/filtering-fors.scala b/tests/rewrites/filtering-fors.scala new file mode 100644 index 000000000000..e3b312186a14 --- /dev/null +++ b/tests/rewrites/filtering-fors.scala @@ -0,0 +1,6 @@ +val xs: List[Any] = ??? +val as = for (x: String) <- xs yield x +val bs = + for + (x: String) <- xs + yield x diff --git a/tests/rewrites/refutable-pattern-bindings.check b/tests/rewrites/refutable-pattern-bindings.check new file mode 100644 index 000000000000..5acea5dc0ead --- /dev/null +++ b/tests/rewrites/refutable-pattern-bindings.check @@ -0,0 +1,25 @@ +val xs: List[Any] = ??? + +val hd :: tl = (xs match + case Nil => null :: xs + case _ => xs): @unchecked + +val h :: t = xs: @unchecked + +val a :: b = + (if xs.isEmpty then null :: xs + else xs): @unchecked + +val c :: d = + (try xs.head :: xs + catch case _: NoSuchElementException => null :: xs): @unchecked + +val e :: f = + {val zero = null :: Nil + if xs.isEmpty then zero + else xs}: @unchecked + +val j :: k = + (for + case (x: String) <- xs + yield x): @unchecked diff --git a/tests/rewrites/refutable-pattern-bindings.scala b/tests/rewrites/refutable-pattern-bindings.scala new file mode 100644 index 000000000000..e724c9564bd2 --- /dev/null +++ b/tests/rewrites/refutable-pattern-bindings.scala @@ -0,0 +1,25 @@ +val xs: List[Any] = ??? + +val hd :: tl = xs match + case Nil => null :: xs + case _ => xs + +val h :: t = xs + +val a :: b = + if xs.isEmpty then null :: xs + else xs + +val c :: d = + try xs.head :: xs + catch case _: NoSuchElementException => null :: xs + +val e :: f = + val zero = null :: Nil + if xs.isEmpty then zero + else xs + +val j :: k = + for + (x: String) <- xs + yield x diff --git a/tests/run-custom-args/tasty-inspector/i14788.scala b/tests/run-custom-args/tasty-inspector/i14788.scala new file mode 100644 index 000000000000..29af31386499 --- /dev/null +++ b/tests/run-custom-args/tasty-inspector/i14788.scala @@ -0,0 +1,55 @@ + +import scala.quoted.* +import scala.tasty.inspector.Inspector +import scala.tasty.inspector.Tasty +import scala.tasty.inspector.TastyInspector +import scala.reflect.ClassTag + +import scala.quoted.* +import scala.tasty.inspector.* + +@main def Test: Unit = { + // Artefact of the current test infrastructure + // TODO improve infrastructure to avoid needing this code on each test + val classpath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(_.contains("runWithCompiler")).get + val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList + val tastyFiles = allTastyFiles.filter(_.contains("MySeq")) + + TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector) +} + +class MySeq(override val length: Int) extends collection.Seq[String] { + def foo: Int = length // error + + def apply(v1: Int): String = ??? + def iterator: Iterator[String] = ??? +} + +class MyInspector extends Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + import quotes.reflect.* + val traverser = new TreeTraverser { + override def traverseTree(tree: Tree)(owner: Symbol): Unit = { + if (tree.isExpr) { + try { + tree.asExpr match { + case '{ ($x: collection.Seq[t]).length } => + super.traverseTree(tree)(owner) + case _ => + super.traverseTree(tree)(owner) + } + } catch { + case e => + report.error(s"unexpected error ${e}", tree.pos) + throw e + } + } else { + super.traverseTree(tree)(owner) + } + } + } + tastys.foreach{ tasty => + traverser.traverseTree(tasty.ast)(tasty.ast.symbol) + } + } +} diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala new file mode 100644 index 000000000000..63b95c280b61 --- /dev/null +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -0,0 +1,129 @@ +import scala.quoted.* +import scala.tasty.inspector.* + +val experimentalDefinitionInLibrary = Set( + + // README + // - Definitions should be grouped under a language feature or API + // - API definitions that must be stabilized at the same time should be added in the same line + // - Language definitions are assumed to be stabilized all at once unless stated otherwise + + + //// New feature: Safe Exceptions + // Can be stabilized when safe exceptions language feature is stabilized. + "scala.CanThrow", + "scala.unsafeExceptions", "scala.unsafeExceptions$", + "scala.runtime.$throws$package$.$throws", + + //// New feature: Tupled Functions + // Can be stabilized when language feature is stabilized. + // Needs user feedback. + // Needs generalization to polymorphic types (at least proof of concept that shows that that design is compatible). + "scala.runtime.TupledFunctions", + "scala.runtime.TupledFunctions$", + "scala.util.TupledFunction", + "scala.util.TupledFunction$", + + //// New feature: main annotation generalization + // Can be stabilized when language feature is stabilized. + // Needs user feedback. + // Should argGetter/varargGetter be simplified? + // Should we have better support for main annotation macros? + "scala.annotation.MainAnnotation", + "scala.annotation.MainAnnotation$", + + //// New APIs: compiletime.ops + // Can be stabilized in 3.3.0 or later. + // Needs user feedback + "scala.compiletime.ops.any$.IsConst", + "scala.compiletime.ops.any$.ToString", + "scala.compiletime.ops.double", "scala.compiletime.ops.double$", + "scala.compiletime.ops.float", + "scala.compiletime.ops.float$", + "scala.compiletime.ops.int$.NumberOfLeadingZeros", + "scala.compiletime.ops.int$.ToDouble", + "scala.compiletime.ops.int$.ToFloat", + "scala.compiletime.ops.int$.ToLong", + "scala.compiletime.ops.long", "scala.compiletime.ops.long$", + "scala.compiletime.ops.string$.Length", + "scala.compiletime.ops.string$.Matches", + "scala.compiletime.ops.string$.Substring", + + //// New APIs: Mirror + // Can be stabilized in 3.2.0 or later. + "scala.deriving.Mirror$.fromTuple", // Probably for 3.2.0 + "scala.deriving.Mirror$.fromProductTyped", // This API is a bit convoluted. We may need some more feedback before we can stabilize it. + + //// New APIs: Tuples + // Should be stabilized in 3.2.0. + "scala.Tuple.:*", "scala.Tuple$.Append", "scala.runtime.Tuples$.append", + "scala.NonEmptyTuple.init", "scala.Tuple$.Init", "scala.runtime.Tuples$.init", + "scala.Tuple$.Last", "scala.NonEmptyTuple.last", "scala.runtime.Tuples$.last", + + //// New APIs: Quotes + // Should be stabilized in 3.2.0. + "scala.quoted.Quotes.reflectModule.AppliedTypeModule.apply", + "scala.quoted.Quotes.reflectModule.SymbolMethods.asQuotes", + "scala.quoted.Quotes.reflectModule.SymbolMethods.termRef", + "scala.quoted.Quotes.reflectModule.SymbolMethods.typeRef", + "scala.quoted.Quotes.reflectModule.TypeReprMethods.substituteTypes", + "scala.quoted.Quotes.reflectModule.TypeReprMethods.typeArgs", + "scala.quoted.Quotes.reflectModule.TypeTreeModule.ref", + // Can be stabilized in 3.2.0 (unsure) or later + "scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings", + // Cant be stabilized yet. + // Need newClass variant that can add constructor parameters. + // Need experimental annotation macros to check that design works. + "scala.quoted.Quotes.reflectModule.ClassDefModule.apply", + "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", +) + + +@main def Test = { + val inspector = new Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + import quotes.reflect.* + val experimentalAnnot = Symbol.requiredClass("scala.annotation.experimental") + object AccumulateExperimentalDefs extends TreeAccumulator[Set[Symbol]]: + def foldTree(expDefs: Set[Symbol], tree: Tree)(owner: Symbol): Set[Symbol] = + tree match + case tree: Definition if tree.symbol.hasAnnotation(experimentalAnnot) => foldOverTree(expDefs + tree.symbol, tree)(owner) + case _ => foldOverTree(expDefs, tree)(owner) + + val experimentalDefinitionsSyms = tastys.foldLeft(Set.empty[Symbol]) { (acc, tasty) => + AccumulateExperimentalDefs.foldTree(acc, tasty.ast)(Symbol.spliceOwner) + } + val experimentalDefinitions = experimentalDefinitionsSyms.map(_.fullName) + val missingFromList = experimentalDefinitions -- experimentalDefinitionInLibrary + val missingInLibrary = experimentalDefinitionInLibrary -- experimentalDefinitions + assert(missingFromList.isEmpty, + s"""Failed @experimental definitions check + | + |Found @experimental definition in library not listed: + |${missingFromList.toSeq.sorted.mkString("\n")} + | + |If added new experimental definitions to the library, add them to the list in tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala + | + |Test only: sbt "scala3-bootstrapped/testCompilation tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala" + |""".stripMargin + ) + assert(missingInLibrary.isEmpty, + s"""Failed @experimental definitions check + | + |Listed @experimental definition was not found in the library + |${missingInLibrary.toSeq.sorted.mkString("\n")} + | + |If experimental definition was removed or stabilized, remove from the list in tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala + | + |Test only: sbt "scala3-bootstrapped/testCompilation tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala" + |""".stripMargin + ) + } + } + + // Artefact of the current test infrastructure + // TODO improve infrastructure to avoid needing this code on each test + val libJarClasspath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(x => x.contains("scala3-library-bootstrapped") && x.endsWith(".jar")).get + + TastyInspector.inspectTastyFilesInJar(libJarClasspath)(inspector) +} diff --git a/tests/run-macros/i12021/Macro_1.scala b/tests/run-macros/i12021/Macro_1.scala index 4c10cf2ca17d..81703dfbab3d 100644 --- a/tests/run-macros/i12021/Macro_1.scala +++ b/tests/run-macros/i12021/Macro_1.scala @@ -9,6 +9,7 @@ def inspect2[A: Type](using Quotes): Expr[String] = { val ps = TypeRepr.of[A].typeSymbol.primaryConstructor.tree match case DefDef(_, List(Nil, ps: TermParamClause), _, _) => ps + case DefDef(_, List(ps: TermParamClause, Nil), _, _) => ps case DefDef(_, List(ps: TermParamClause), _, _) => ps val names = ps.params.map(p => s"${p.name}: ${p.tpt.show}").mkString("(", ", ", ")") diff --git a/tests/run/backwardsCompat-implicitParens.check b/tests/run/backwardsCompat-implicitParens.check new file mode 100644 index 000000000000..1a471019f925 --- /dev/null +++ b/tests/run/backwardsCompat-implicitParens.check @@ -0,0 +1,20 @@ +Bar +Bar +Bar +Bar +() +Bat +Bat +Bat +Bat +() +Bax +Bax +Bax +Bax +() +Baz +Baz +Baz +Baz +() diff --git a/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala b/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala new file mode 100644 index 000000000000..77a77baf86e6 --- /dev/null +++ b/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala @@ -0,0 +1,32 @@ +class Bar(using x: Int)(y: String): + override def toString = "Bar" +object Bar: + given Int = 1 + inline def foo = + println(new Bar()("")) + println(Bar()("")) + +class Bat(using x: Int): + override def toString = "Bat" +object Bat: + given Int = 1 + inline def foo = + println(new Bat()) + println(Bat()) + +class Bax(using x: Int)(): + override def toString = "Bax" +object Bax: + given Int = 1 + inline def foo = + println(new Bax()) + println(Bax()) + +class Baz(using x: Int)(using y: String): + override def toString = "Baz" +object Baz: + given Int = 1 + given String = "x" + inline def foo = + println(new Baz()) + println(Baz()) diff --git a/tests/run/backwardsCompat-implicitParens/Test_2.scala b/tests/run/backwardsCompat-implicitParens/Test_2.scala new file mode 100644 index 000000000000..b22b5b411b0c --- /dev/null +++ b/tests/run/backwardsCompat-implicitParens/Test_2.scala @@ -0,0 +1,19 @@ +@main def Test = + given Int = 1 + given String = "x" + + println(new Bar("")) + println(Bar("")) + println(Bar.foo) + + println(new Bat()) + println(Bat()) + println(Bat.foo) + + println(new Bax()) + println(Bax()) + println(Bax.foo) + + println(new Baz()) + println(Baz()) + println(Baz.foo) diff --git a/tests/run/i14164.scala b/tests/run/i14164.scala new file mode 100644 index 000000000000..2a8476625634 --- /dev/null +++ b/tests/run/i14164.scala @@ -0,0 +1,27 @@ + +object Test: + class Base(a: String = "x", param: String) + + class Child extends Base( + param = + for x <- Seq("a") yield x + "param" + ) + + new Child + + def main(args: Array[String]) = () + +end Test + +class Test2: + class Inner(withDefault: String = "inner")( + dependentDefault: String = withDefault) extends Object { + def this(x: Int) = this(x.toString)() + } + +class Test3: + class Inner(withDefault: () => String = () => "inner")( + dependentDefault: String = withDefault()) extends Object { + def this(x: Int) = this(() => x.toString)() + } diff --git a/tests/run/i2567.scala b/tests/run/i2567.scala index c9625aeb33d1..4cd60080faa7 100644 --- a/tests/run/i2567.scala +++ b/tests/run/i2567.scala @@ -10,7 +10,7 @@ object Test extends App { new Foo new Foo(using tc) new Foo() - new Foo()(using tc) + new Foo(using tc) Foo() - Foo()(using tc) + Foo(using tc) } \ No newline at end of file diff --git a/tests/run/main-annotation-example.check b/tests/run/main-annotation-example.check new file mode 100644 index 000000000000..97fcf11da08b --- /dev/null +++ b/tests/run/main-annotation-example.check @@ -0,0 +1,3 @@ +executing program +result: 28 +executed program diff --git a/tests/run/main-annotation-example.scala b/tests/run/main-annotation-example.scala new file mode 100644 index 000000000000..954278d6b26f --- /dev/null +++ b/tests/run/main-annotation-example.scala @@ -0,0 +1,62 @@ +import scala.annotation.* +import collection.mutable +import scala.util.CommandLineParser.FromString + +/** Sum all the numbers + * + * @param first Fist number to sum + * @param rest The rest of the numbers to sum + */ +@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum + + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("sum") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("23", "2", "3")) +end Test + +@experimental +class myMain extends MainAnnotation[FromString, Int]: + import MainAnnotation.{ Info, Parameter } + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = + if args.contains("--help") then + println(info.documentation) + None // do not parse or run the program + else if info.parameters.exists(_.hasDefault) then + println("Default arguments are not supported") + None + else if info.hasVarargs then + val numPlainArgs = info.parameters.length - 1 + if numPlainArgs > args.length then + println("Not enough arguments") + None + else + Some(args) + else + if info.parameters.length > args.length then + println("Not enough arguments") + None + else if info.parameters.length < args.length then + println("Too many arguments") + None + else + Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = + () => parser.fromString(arg) + + def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] = + () => args.map(arg => parser.fromString(arg)) + + def run(program: () => Int): Unit = + println("executing program") + val result = program() + println("result: " + result) + println("executed program") +end myMain diff --git a/tests/run/main-annotation-homemade-annot-1.check b/tests/run/main-annotation-homemade-annot-1.check new file mode 100644 index 000000000000..4b7ff457bb11 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-1.check @@ -0,0 +1,4 @@ +42 +42 +1 +2 diff --git a/tests/run/main-annotation-homemade-annot-1.scala b/tests/run/main-annotation-homemade-annot-1.scala new file mode 100644 index 000000000000..daf27b944d99 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-1.scala @@ -0,0 +1,46 @@ +import scala.concurrent._ +import scala.annotation.* +import scala.collection.mutable +import ExecutionContext.Implicits.global +import duration._ +import util.CommandLineParser.FromString + +@mainAwait def get(wait: Int): Future[Int] = Future{ + Thread.sleep(1000 * wait) + 42 +} + +@mainAwait def getMany(wait: Int*): Future[Int] = Future{ + Thread.sleep(1000 * wait.sum) + wait.length +} + +object Test: + def callMain(cls: String, args: Array[String]): Unit = + val clazz = Class.forName(cls) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + println(Await.result(get(1), Duration(2, SECONDS))) + callMain("get", Array("1")) + callMain("getMany", Array("1")) + callMain("getMany", Array("0", "1")) +end Test + +@experimental +class mainAwait(timeout: Int = 2) extends MainAnnotation[FromString, Future[Any]]: + import MainAnnotation.* + + // This is a toy example, it only works with positional args + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = + () => p.fromString(arg) + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = + () => for arg <- args yield p.fromString(arg) + + def run(f: () => Future[Any]): Unit = println(Await.result(f(), Duration(timeout, SECONDS))) + +end mainAwait diff --git a/tests/run/main-annotation-homemade-annot-2.check b/tests/run/main-annotation-homemade-annot-2.check new file mode 100644 index 000000000000..f57ec79b8dbd --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-2.check @@ -0,0 +1,11 @@ +I was run! +A +I was run! +A +I was run! +A +Here are some colors: +Purple smart, Blue fast, White fashion, Yellow quiet, Orange honest, Pink loud +This will be printed, but nothing more. +This will be printed, but nothing more. +This will be printed, but nothing more. diff --git a/tests/run/main-annotation-homemade-annot-2.scala b/tests/run/main-annotation-homemade-annot-2.scala new file mode 100644 index 000000000000..3cee9151282d --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-2.scala @@ -0,0 +1,49 @@ +import scala.collection.mutable +import scala.annotation.* +import util.CommandLineParser.FromString + +@myMain()("A") +def foo1(): Unit = println("I was run!") + +@myMain(0)("This should not be printed") +def foo2() = throw new Exception("This should not be run") + +@myMain(1)("Purple smart", "Blue fast", "White fashion", "Yellow quiet", "Orange honest", "Pink loud") +def foo3() = println("Here are some colors:") + +@myMain()() +def foo4() = println("This will be printed, but nothing more.") + +object Test: + val allClazzes: Seq[Class[?]] = + LazyList.from(1).map(i => scala.util.Try(Class.forName("foo" + i.toString))).takeWhile(_.isSuccess).map(_.get) + + def callMains(): Unit = + for (clazz <- allClazzes) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) + + def main(args: Array[String]) = + callMains() +end Test + +// This is a toy example, it only works with positional args +@experimental +class myMain(runs: Int = 3)(after: String*) extends MainAnnotation[FromString, Any]: + import MainAnnotation.* + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = + () => p.fromString(arg) + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = + () => for arg <- args yield p.fromString(arg) + + def run(f: () => Any): Unit = + for (_ <- 1 to runs) + f() + if after.length > 0 then println(after.mkString(", ")) + end run + +end myMain diff --git a/tests/run/main-annotation-homemade-annot-3.check b/tests/run/main-annotation-homemade-annot-3.check new file mode 100644 index 000000000000..cd0875583aab --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-3.check @@ -0,0 +1 @@ +Hello world! diff --git a/tests/run/main-annotation-homemade-annot-3.scala b/tests/run/main-annotation-homemade-annot-3.scala new file mode 100644 index 000000000000..3fc42abcce79 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-3.scala @@ -0,0 +1,23 @@ +import scala.annotation.* +import scala.util.CommandLineParser.FromString + +@mainNoArgs def foo() = println("Hello world!") + +object Test: + def main(args: Array[String]) = + val clazz = Class.forName("foo") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) +end Test + +@experimental +class mainNoArgs extends MainAnnotation[FromString, Any]: + import MainAnnotation.* + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? + + def run(program: () => Any): Unit = program() diff --git a/tests/run/main-annotation-homemade-annot-4.check b/tests/run/main-annotation-homemade-annot-4.check new file mode 100644 index 000000000000..cd0875583aab --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-4.check @@ -0,0 +1 @@ +Hello world! diff --git a/tests/run/main-annotation-homemade-annot-4.scala b/tests/run/main-annotation-homemade-annot-4.scala new file mode 100644 index 000000000000..0dbd006ee5b1 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-4.scala @@ -0,0 +1,24 @@ +import scala.annotation.* +import scala.util.CommandLineParser.FromString + +@mainManyArgs(1, "B", 3) def foo() = println("Hello world!") + +object Test: + def main(args: Array[String]) = + val clazz = Class.forName("foo") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) +end Test + +@experimental +class mainManyArgs(i1: Int, s2: String, i3: Int) extends MainAnnotation[FromString, Any]: + import MainAnnotation.* + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? + + + def run(program: () => Any): Unit = program() diff --git a/tests/run/main-annotation-homemade-annot-5.check b/tests/run/main-annotation-homemade-annot-5.check new file mode 100644 index 000000000000..7d60d6656c81 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-5.check @@ -0,0 +1,2 @@ +Hello world! +Hello world! diff --git a/tests/run/main-annotation-homemade-annot-5.scala b/tests/run/main-annotation-homemade-annot-5.scala new file mode 100644 index 000000000000..d61cd55eb852 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-5.scala @@ -0,0 +1,25 @@ +import scala.annotation.* +import scala.util.CommandLineParser.FromString + +@mainManyArgs(Some(1)) def foo() = println("Hello world!") +@mainManyArgs(None) def bar() = println("Hello world!") + +object Test: + def main(args: Array[String]) = + for (methodName <- List("foo", "bar")) + val clazz = Class.forName(methodName) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) +end Test + +@experimental +class mainManyArgs(o: Option[Int]) extends MainAnnotation[FromString, Any]: + import MainAnnotation.* + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? + + def run(program: () => Any): Unit = program() diff --git a/tests/run/main-annotation-homemade-annot-6.check b/tests/run/main-annotation-homemade-annot-6.check new file mode 100644 index 000000000000..5cc6c07e1f56 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-6.check @@ -0,0 +1,25 @@ +command( + Array(1, 2), + foo, + "Foo docs", + Seq( + Parameter(name="i", typeName="scala.Int", hasDefault=false, isVarargs=false, documentation="", annotations=List()), + Parameter(name="j", typeName="java.lang.String", hasDefault=true, isVarargs=false, documentation="", annotations=List()) + )* +) +run() +foo(42, abc) + +command( + Array(1, 2), + bar, + "Bar docs", + Seq( + Parameter(name="i", typeName="scala.collection.immutable.List[Int]", hasDefault=false, isVarargs=false, documentation="the first parameter", annotations=List(MyParamAnnot(3))), + Parameter(name="rest", typeName="scala.Int", hasDefault=false, isVarargs=true, documentation="", annotations=List()) + )* +) +varargGetter() +run() +bar(List(42), 42, 42) + diff --git a/tests/run/main-annotation-homemade-annot-6.scala b/tests/run/main-annotation-homemade-annot-6.scala new file mode 100644 index 000000000000..9ba0b31fc689 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-6.scala @@ -0,0 +1,62 @@ +import scala.annotation.* + +/** Foo docs */ +@myMain def foo(i: Int, j: String = "2") = println(s"foo($i, $j)") +/** Bar docs + * + * @param i the first parameter + */ +@myMain def bar(@MyParamAnnot(3) i: List[Int], rest: Int*) = println(s"bar($i, ${rest.mkString(", ")})") + +object Test: + def main(args: Array[String]) = + for (methodName <- List("foo", "bar")) + val clazz = Class.forName(methodName) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]("1", "2")) +end Test + +@experimental +class myMain extends MainAnnotation[Make, Any]: + import MainAnnotation.* + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = + def paramInfoString(paramInfo: Parameter) = + import paramInfo.* + s" Parameter(name=\"$name\", typeName=\"$typeName\", hasDefault=$hasDefault, isVarargs=$isVarargs, documentation=\"$documentation\", annotations=$annotations)" + println( + s"""command( + | ${args.mkString("Array(", ", ", ")")}, + | ${info.name}, + | "${info.documentation}", + | ${info.parameters.map(paramInfoString).mkString("Seq(\n", ",\n", "\n )*")} + |)""".stripMargin) + Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: Make[T]): () => T = + () => p.make + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: Make[T]): () => Seq[T] = + println("varargGetter()") + () => Seq(p.make, p.make) + + def run(f: () => Any): Unit = + println("run()") + f() + println() + +@experimental +case class MyParamAnnot(n: Int) extends MainAnnotation.ParameterAnnotation + +trait Make[T]: + def make: T + +given Make[Int] with + def make: Int = 42 + + +given Make[String] with + def make: String = "abc" + +given [T: Make]: Make[List[T]] with + def make: List[T] = List(summon[Make[T]].make) diff --git a/tests/run/main-annotation-newMain.scala b/tests/run/main-annotation-newMain.scala new file mode 100644 index 000000000000..9e85d5f948cc --- /dev/null +++ b/tests/run/main-annotation-newMain.scala @@ -0,0 +1,320 @@ +import scala.annotation.* +import collection.mutable +import scala.util.CommandLineParser.FromString + +@newMain def happyBirthday(age: Int, name: String, others: String*) = + val suffix = + age % 100 match + case 11 | 12 | 13 => "th" + case _ => + age % 10 match + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do bldr.append(" and ").append(other) + println(bldr) + + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("happyBirthday") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("23", "Lisa", "Peter")) +end Test + + + +@experimental +final class newMain extends MainAnnotation[FromString, Any]: + import newMain._ + import MainAnnotation._ + + private inline val argMarker = "--" + private inline val shortArgMarker = "-" + + /** The name of the special argument to display the method's help. + * If one of the method's parameters is called the same, will be ignored. + */ + private inline val helpArg = "help" + + /** The short name of the special argument to display the method's help. + * If one of the method's parameters uses the same short name, will be ignored. + */ + private inline val shortHelpArg = 'h' + + private inline val maxUsageLineLength = 120 + + private var info: Info = _ // TODO remove this var + + + /** A buffer for all errors */ + private val errors = new mutable.ArrayBuffer[String] + + /** Issue an error, and return an uncallable getter */ + private def error(msg: String): () => Nothing = + errors += msg + () => throw new AssertionError("trying to get invalid argument") + + private def getAliases(param: Parameter): Seq[String] = + param.annotations.collect{ case a: Alias => a }.flatMap(_.aliases) + + private def getAlternativeNames(param: Parameter): Seq[String] = + getAliases(param).filter(nameIsValid(_)) + + private def getShortNames(param: Parameter): Seq[Char] = + getAliases(param).filter(shortNameIsValid(_)).map(_(0)) + + private inline def nameIsValid(name: String): Boolean = + name.length > 1 // TODO add more checks for illegal characters + + private inline def shortNameIsValid(name: String): Boolean = + name.length == 1 && shortNameIsValidChar(name(0)) + + private inline def shortNameIsValidChar(shortName: Char): Boolean = + ('A' <= shortName && shortName <= 'Z') || ('a' <= shortName && shortName <= 'z') + + private def getNameWithMarker(name: String | Char): String = name match { + case c: Char => shortArgMarker + c + case s: String if shortNameIsValid(s) => shortArgMarker + s + case s => argMarker + s + } + + private def getInvalidNames(param: Parameter): Seq[String | Char] = + getAliases(param).filter(name => !nameIsValid(name) && !shortNameIsValid(name)) + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = + this.info = info + + val namesToCanonicalName: Map[String, String] = info.parameters.flatMap( + infos => + val names = getAlternativeNames(infos) + val canonicalName = infos.name + if nameIsValid(canonicalName) then (canonicalName +: names).map(_ -> canonicalName) + else names.map(_ -> canonicalName) + ).toMap + val shortNamesToCanonicalName: Map[Char, String] = info.parameters.flatMap( + infos => + val names = getShortNames(infos) + val canonicalName = infos.name + if shortNameIsValid(canonicalName) then (canonicalName(0) +: names).map(_ -> canonicalName) + else names.map(_ -> canonicalName) + ).toMap + + val helpIsOverridden = namesToCanonicalName.exists((name, _) => name == helpArg) + val shortHelpIsOverridden = shortNamesToCanonicalName.exists((name, _) => name == shortHelpArg) + + val (positionalArgs, byNameArgs, invalidByNameArgs) = { + def getCanonicalArgName(arg: String): Option[String] = + if arg.startsWith(argMarker) && arg.length > argMarker.length then + namesToCanonicalName.get(arg.drop(argMarker.length)) + else if arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 then + shortNamesToCanonicalName.get(arg(shortArgMarker.length)) + else + None + + def isArgName(arg: String): Boolean = + val isFullName = arg.startsWith(argMarker) + val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValidChar(arg(shortArgMarker.length)) + isFullName || isShortName + + def recurse(remainingArgs: Seq[String], pa: mutable.Queue[String], bna: Seq[(String, String)], ia: Seq[String]): (mutable.Queue[String], Seq[(String, String)], Seq[String]) = + remainingArgs match { + case Seq() => + (pa, bna, ia) + case argName +: argValue +: rest if isArgName(argName) => + getCanonicalArgName(argName) match { + case Some(canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue), ia) + case None => recurse(rest, pa, bna, ia :+ argName) + } + case arg +: rest => + recurse(rest, pa :+ arg, bna, ia) + } + + val (pa, bna, ia) = recurse(args.toSeq, mutable.Queue.empty, Vector(), Vector()) + val nameToArgValues: Map[String, Seq[String]] = if bna.isEmpty then Map.empty else bna.groupMapReduce(_._1)(p => List(p._2))(_ ++ _) + (pa, nameToArgValues, ia) + } + + val argStrings: Seq[Seq[String]] = + for paramInfo <- info.parameters yield { + if (paramInfo.isVarargs) { + val byNameGetters = byNameArgs.getOrElse(paramInfo.name, Seq()) + val positionalGetters = positionalArgs.removeAll() + // First take arguments passed by name, then those passed by position + byNameGetters ++ positionalGetters + } else { + byNameArgs.get(paramInfo.name) match + case Some(Nil) => + throw AssertionError(s"${paramInfo.name} present in byNameArgs, but it has no argument value") + case Some(argValues) => + if argValues.length > 1 then + // Do not accept multiple values + // Remove this test to take last given argument + error(s"more than one value for ${paramInfo.name}: ${argValues.mkString(", ")}") + Nil + else + List(argValues.last) + case None => + if positionalArgs.length > 0 then + List(positionalArgs.dequeue()) + else if paramInfo.hasDefault then + Nil + else + error(s"missing argument for ${paramInfo.name}") + Nil + } + } + + // Check aliases unicity + val nameAndCanonicalName = info.parameters.flatMap { + case paramInfo => (paramInfo.name +: getAlternativeNames(paramInfo) ++: getShortNames(paramInfo)).map(_ -> paramInfo.name) + } + val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2) + + for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 do + throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}") + + // Check aliases validity + val problematicNames = info.parameters.flatMap(getInvalidNames) + if problematicNames.length > 0 then + throw IllegalArgumentException(s"The following aliases are invalid: ${problematicNames.mkString(", ")}") + + // Handle unused and invalid args + for (remainingArg <- positionalArgs) error(s"unused argument: $remainingArg") + for (invalidArg <- invalidByNameArgs) error(s"unknown argument name: $invalidArg") + + val displayHelp = + (!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) || + (!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg))) + + if displayHelp then + usage() + println() + explain() + None + else if errors.nonEmpty then + for msg <- errors do println(s"Error: $msg") + usage() + None + else + Some(argStrings.flatten) + end command + + private def usage(): Unit = + def argsUsage: Seq[String] = + for (infos <- info.parameters) + yield { + val canonicalName = getNameWithMarker(infos.name) + val shortNames = getShortNames(infos).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) + val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]") + val shortTypeName = infos.typeName.split('.').last + if infos.isVarargs then s"[<$shortTypeName> [<$shortTypeName> [...]]]" + else if infos.hasDefault then s"[$namesPrint <$shortTypeName>]" + else s"$namesPrint <$shortTypeName>" + } + + def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = { + def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] = + (args, currentLine) match { + case (Nil, "") => acc + case (Nil, l) => (acc :+ l) + case (arg +: t, "") => recurse(t, arg, acc) + case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc) + case (arg +: t, l) => recurse(t, arg, acc :+ l) + } + + recurse(argsUsage, "", Vector()).toList + } + + val usageBeginning = s"Usage: ${info.name} " + val argsOffset = usageBeginning.length + val usages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset) + + println(usageBeginning + usages.mkString("\n" + " " * argsOffset)) + end usage + + private def explain(): Unit = + inline def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n") + + def wrapLongLine(line: String, maxLength: Int): List[String] = { + def recurse(s: String, acc: Vector[String]): Seq[String] = + val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength) + if ((s.length <= maxLength) || (lastSpace < 0)) + acc :+ s + else { + val (shortLine, rest) = s.splitAt(lastSpace) + recurse(rest.trim.nn, acc :+ shortLine) + } + + recurse(line, Vector()).toList + } + + if (info.documentation.nonEmpty) + println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n")) + if (info.parameters.nonEmpty) { + val argNameShift = 2 + val argDocShift = argNameShift + 2 + + println("Arguments:") + for infos <- info.parameters do + val canonicalName = getNameWithMarker(infos.name) + val shortNames = getShortNames(infos).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) + val otherNames = (alternativeNames ++: shortNames) match { + case Seq() => "" + case names => names.mkString("(", ", ", ") ") + } + val argDoc = StringBuilder(" " * argNameShift) + argDoc.append(s"$canonicalName $otherNames- ${infos.typeName.split('.').last}") + if infos.isVarargs then argDoc.append(" (vararg)") + else if infos.hasDefault then argDoc.append(" (optional)") + + if (infos.documentation.nonEmpty) { + val shiftedDoc = + infos.documentation.split("\n").nn + .map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift)) + .mkString("\n") + argDoc.append("\n").append(shiftedDoc) + } + + println(argDoc) + } + end explain + + private def convert[T](argName: String, arg: String, p: FromString[T]): () => T = + p.fromStringOption(arg) match + case Some(t) => () => t + case None => error(s"invalid argument for $argName: $arg") + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = { + if arg.nonEmpty then convert(param.name, arg, p) + else defaultArgument match + case Some(defaultGetter) => defaultGetter + case None => error(s"missing argument for ${param.name}") + } + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = { + val getters = args.map(arg => convert(param.name, arg, p)) + () => getters.map(_()) + } + + def run(execProgram: () => Any): Unit = { + if errors.nonEmpty then + for msg <- errors do println(s"Error: $msg") + usage() + else + execProgram() + } + +end newMain + +object newMain: + @experimental + final class Alias(val aliases: String*) extends MainAnnotation.ParameterAnnotation +end newMain diff --git a/tests/run/tailrec-return.check b/tests/run/tailrec-return.check new file mode 100644 index 000000000000..361e76d8a285 --- /dev/null +++ b/tests/run/tailrec-return.check @@ -0,0 +1,7 @@ +6 +false +true +false +true +Ada Lovelace, Alan Turing +List(9, 10) diff --git a/tests/run/tailrec-return.scala b/tests/run/tailrec-return.scala new file mode 100644 index 000000000000..aa760960403d --- /dev/null +++ b/tests/run/tailrec-return.scala @@ -0,0 +1,66 @@ +object Test: + + @annotation.tailrec + def sum(n: Int, acc: Int = 0): Int = + if n != 0 then return sum(n - 1, acc + n) + acc + + @annotation.tailrec + def isEven(n: Int): Boolean = + if n != 0 && n != 1 then return isEven(n - 2) + if n == 1 then return false + true + + @annotation.tailrec + def isEvenApply(n: Int): Boolean = + // Return inside an `Apply.fun` + ( + if n != 0 && n != 1 then return isEvenApply(n - 2) + else if n == 1 then return false + else (x: Boolean) => x + )(true) + + @annotation.tailrec + def isEvenWhile(n: Int): Boolean = + // Return inside a `WhileDo.cond` + while( + if n != 0 && n != 1 then return isEvenWhile(n - 2) + else if n == 1 then return false + else true + ) {} + true + + @annotation.tailrec + def isEvenReturn(n: Int): Boolean = + // Return inside a `Return` + return + if n != 0 && n != 1 then return isEvenReturn(n - 2) + else if n == 1 then return false + else true + + @annotation.tailrec + def names(l: List[(String, String) | Null], acc: List[String] = Nil): List[String] = + l match + case Nil => acc.reverse + case x :: xs => + if x == null then return names(xs, acc) + + val displayName = x._1 + " " + x._2 + names(xs, displayName :: acc) + + def nonTail(l: List[Int]): List[Int] = + l match + case Nil => Nil + case x :: xs => + // The call to nonTail should *not* be eliminated + (x + 1) :: nonTail(xs) + + + def main(args: Array[String]): Unit = + println(sum(3)) + println(isEven(5)) + println(isEvenApply(6)) + println(isEvenWhile(7)) + println(isEvenReturn(8)) + println(names(List(("Ada", "Lovelace"), null, ("Alan", "Turing"))).mkString(", ")) + println(nonTail(List(8, 9))) diff --git a/tests/run/wildcard-vals.scala b/tests/run/wildcard-vals.scala new file mode 100644 index 000000000000..e972a14c445d --- /dev/null +++ b/tests/run/wildcard-vals.scala @@ -0,0 +1,14 @@ +object O: + val _ = 1 + val _ = 2 + val _: Int = 3 + val _: Int = 4 + def x = 2 + +@main def Test = + assert(O.x == 2) + val _ = 1 + val _ = 2 + val _: Int = 3 + val _: Int = 4 + diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 12c6f72bdb87..d761489ded6c 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -1848,7 +1848,7 @@ givens/InventedNames$package.given_String. => final implicit lazy val given meth givens/InventedNames$package.given_X. => final implicit given object given_X extends Object with X { self: given_X.type => +2 decls } givens/InventedNames$package.given_X.doX(). => method doX => Int <: givens/X#doX(). givens/InventedNames$package.given_Y# => implicit given class given_Y extends Object with Y { self: given_Y => +3 decls } -givens/InventedNames$package.given_Y#``(). => primary ctor ()(implicit val given param x$1: X): given_Y +givens/InventedNames$package.given_Y#``(). => primary ctor (implicit val given param x$1: X)(): given_Y givens/InventedNames$package.given_Y#``().(x$1) => implicit val given param x$1: X givens/InventedNames$package.given_Y#doY(). => method doY => String <: givens/Y#doY(). givens/InventedNames$package.given_Y#x$1. => protected implicit val given method x$1 X diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala index 08aa26726dc7..9561c0866e7e 100644 --- a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala @@ -119,6 +119,16 @@ object RegressionTestScala3 { def bar(x: Long = 0): Foo = new Foo(x) } } + + object Issue14896 { + val obj = new js.Object { + val a = 42 + val b = "foo" + } + + val entries = js.Object.entries(obj) + val js.Tuple2(k, v) = entries(0): @unchecked + } } // This class needs to be at the top-level, not in an object, to reproduce the issue