diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..d6fb7f9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: lihaoyi diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e33811f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..6aded51 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,77 @@ +name: ci + +on: + push: + branches: + - main + tags: + - '*' + pull_request: + branches: + - main + +jobs: + + test-jvm: + runs-on: ubuntu-latest + strategy: + matrix: + java: ['8', '17'] + env: + JAVA_OPTS: "-Xss10M" + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Run JVM tests + run: | + ./mill -i "unroll[_].tests[_].__.run" + + + publish-sonatype: + if: github.repository == 'com-lihaoyi/unroll' && contains(github.ref, 'refs/tags/') + needs: + - test-jvm + runs-on: ubuntu-latest + env: + SONATYPE_PGP_PRIVATE_KEY: ${{ secrets.SONATYPE_PGP_PRIVATE_KEY }} + SONATYPE_PGP_PRIVATE_KEY_PASSWORD: ${{ secrets.SONATYPE_PGP_PRIVATE_KEY_PASSWORD }} + SONATYPE_USER: ${{ secrets.SONATYPE_USER }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + LANG: "en_US.UTF-8" + LC_MESSAGES: "en_US.UTF-8" + LC_ALL: "en_US.UTF-8" + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 8 + - name: Publish to Maven Central + run: | + if [[ $(git tag --points-at HEAD) != '' ]]; then + echo $SONATYPE_PGP_PRIVATE_KEY | base64 --decode > gpg_key + gpg --import --no-tty --batch --yes gpg_key + rm gpg_key + ./mill -i mill.scalalib.PublishModule/publishAll \ + --sonatypeCreds $SONATYPE_USER:$SONATYPE_PASSWORD \ + --gpgArgs --passphrase=$SONATYPE_PGP_PRIVATE_KEY_PASSWORD,--no-tty,--pinentry-mode,loopback,--batch,--yes,-a,-b \ + --publishArtifacts __.publishArtifacts \ + --readTimeout 600000 \ + --awaitTimeout 600000 \ + --release true \ + --signed true + fi + - name: Create GitHub Release + id: create_gh_release + uses: actions/create-release@v1.1.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bb56ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/*/target/ +target/ +output/ +.DS_STORE +.idea_modules +.idea +.vscode/ +out/ +/.bloop/ +/.metals/ +mill.iml +.bsp/ +bsp.log +lowered.hnir +.dotty-ide* diff --git a/README.md b/README.md new file mode 100644 index 0000000..e921c9a --- /dev/null +++ b/README.md @@ -0,0 +1,200 @@ +# unroll + + +Unroll provides the `@unroll.Unroll("foo")` annotation that can be applied +to methods, classes, and constructors. `@Unroll` generates unrolled/telescoping +versions of the method, starting from the parameter specified by `"foo"`, which +are simple forwarders to the primary method or constructor implementation. + +In the past, evolving code in Scala while maintaining binary compatibility was a pain. +You couldn't use default parameters, you couldn't use case classes. Many people fell +back to Java-style builder patterns and factories with `.withFoo` everywhere to maintain binary +compatibility. Or you would tediously define tons of binary compatibility stub methods +that just copy-paste the original signature and forward the call to the new implementation. + +In effect, you often gave up everything that made Scala nice to read and write, because +the alternative was worse: every time you added a new parameter to a method, even though +it has a default value, all your users would have to recompile all their code. And all +*their* users would need to re-compile all their code, transitively. And so library +maintainers would suffer so their users could have a smooth upgrading experience. + +With `@Unroll`, none of this is a problem anymore. You can add new parameters +where-ever you like: method `def`s, `class`es, `case class`es, etc. As long as the +new parameter has a default value, you can `@Unroll` it to generate the binary-compatibility +stub forwarder method automatically. Happy library maintainers, happy users, everyone is happy! + +See this original discussion for more context: + +* https://contributors.scala-lang.org/t/can-we-make-adding-a-parameter-with-a-default-value-binary-compatible + +## Usage + +### Methods + +```scala +import unroll.Unroll + +object Unrolled{ + def foo(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0) = s + n + b + l +} +``` + +Unrolls to: + +```scala +import unroll.Unroll + +object Unrolled{ + def foo(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0) = s + n + b + l + + def foo(s: String, n: Int, b: Boolean) = foo(s, n, b, 0) + def foo(s: String, n: Int) = foo(s, n, true, 0) +} +```` +### Classes + +```scala +import unroll.Unroll + +class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0){ + def foo = s + n + b + l +} +``` + +Unrolls to: + +```scala +import unroll.Unroll + +class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0){ + def foo = s + n + b + l + + def this(s: String, n: Int, b: Boolean) = this(s, n, b, 0) + def this(s: String, n: Int) = this(s, n, true, 0) +} +``` + +### Constructors + +```scala +import unroll.Unroll + +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0) = { + this() + foo = s + n + b + l + } +} +``` + +Unrolls to: + +```scala +import unroll.Unroll + +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0) = { + this() + foo = s + n + b + l + } + + def this(s: String, n: Int, b: Boolean) = this(s, n, b, 0) + def this(s: String, n: Int) = this(s, n, true, 0) +} +``` + +### Case Classes + +```scala +import unroll.Unroll + +case class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true){ + def foo = s + n + b +} +``` + +Unrolls to: + +```scala +import unroll.Unroll + +case class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0L){ + def this(s: String, n: Int) = this(s, n, true, 0L) + def this(s: String, n: Int, b: Boolean) = this(s, n, b, 0L) + + def copy(s: String, n: Int) = copy(s, n, true, 0L) + def copy(s: String, n: Int, b: Boolean) = copy(s, n, b, 0L) + + def foo = s + n + b +} +object Unrolled{ + def apply(s: String, n: Int) = apply(s, n, true, 0L) + def apply(s: String, n: Int, b: Boolean) = apply(s, n, b, , 0L) +} +``` + + +## Limitations + +1. Only the first parameter list of multi-parameter list methods (i.e. curried or taking + implicits) can be unrolled. This is an implementation restriction that may be lifted + with a bit of work + +2. As unrolling generates synthetic forwarder methods for binary compatibility, it is + possible for them to collide if your unrolled method has manually-defined overloads + +3. Unrolled case classes are only fully binary compatible in Scala 3, though they are + _almost_ binary compatible in Scala 2. Direct calls to `unapply` are binary incompatible, + but most common pattern matching of `case class`es goes through a different code path + that _is_ binary compatible. In practice this should be sufficient for 99% of use cases, + but it does means that it is possible for code written as below to fail in Scala 2 + if a new unrolled parameter is added to the case class `Unrolled`. + +```scala +def foo(t: (String, Int)) = println(t) +Unrolled.unapply(unrolled).map(foo) +``` + +`unapply` is not a binary compatibility issue in Scala 3, even when called directly, due to +[Option-less Pattern Matching](https://docs.scala-lang.org/scala3/reference/changed-features/pattern-matching.html) + +## Testing + +Unroll is tested via a range of test-cases: `classMethod`, `objectMethod`, etc. These +are organized in `build.sc`, to take advantage of the build system's ability to wire up +compilation and classpaths + +Each of these cases has three versions, `v1` `v2` `v3`, each of which has +different numbers of default parameters + +For each test-case, we have the following tests: + +1. `unroll[].tests[]`: Using java reflection to make + sure the correct methods are generated in version `v3` and are callable with the + correct output + +2. `unroll[].tests[].{v1,v2,v3}.test`: Tests compiled against + the respective version of a test-case and running against the same version + +3. `unroll[].tests[].{v1v2,v2v3,v1v3}.test`: Tests compiled + an *older* version of a test-case but running against *newer* version. This simulates + a downstream library compiled against an older version of an upstream library but + running against a newer version, ensuring there is backwards binary compatibility + +4. `unroll[].tests[].{v1,v2,v3}.mimaReportBinaryIssues`: Running + the Scala [MiMa Migration Manager](https://github.com/lightbend/mima) to check a newer + version of test-case against an older version for binary compatibility + +You can also run the following command to run all tests: + +```bash +./mill -i -w "unroll[_].tests.__.run" +``` + +This can be useful as a final sanity check, even though you usually want to run +a subset of the tests specific to the `scala-version` and `test-case` you are +interested in. diff --git a/build.sc b/build.sc new file mode 100644 index 0000000..3f05ed4 --- /dev/null +++ b/build.sc @@ -0,0 +1,195 @@ +import mill._, scalalib._, publish._ +import $ivy.`com.github.lolgab::mill-mima::0.1.0` +import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.4.0` +import de.tobiasroeser.mill.vcs.version.VcsVersion +import com.github.lolgab.mill.mima.{CheckDirection, ProblemFilter, Mima} +import com.github.lolgab.mill.mima.worker.MimaBuildInfo +import com.github.lolgab.mill.mima.IncompatibleSignatureProblem +import com.github.lolgab.mill.mima.worker +import com.github.lolgab.mill.mima.worker.MimaWorkerExternalModule + +import scala.util.chaining._ + +val scala212 = "2.12.18" +val scala213 = "2.13.12" +val scala3 = "3.3.1" + +val scalaVersions = Seq(scala212, scala213, scala3) + + +object unroll extends Cross[UnrollModule](scalaVersions) +trait UnrollModule extends Cross.Module[String]{ + trait InnerScalaModule extends CrossScalaModule { + def crossValue = UnrollModule.this.crossValue + + override def artifactNameParts = millModuleSegments.parts.patch(1, Nil, 1) + } + + trait InnerPublishModule extends InnerScalaModule with PublishModule{ + + def publishVersion = VcsVersion.vcsState().format() + + def pomSettings = PomSettings( + description = "Main method argument parser for Scala", + organization = "com.lihaoyi", + url = "https://github.com/com-lihaoyi/unroll", + licenses = Seq(License.MIT), + versionControl = VersionControl.github("com-lihaoyi", "unroll"), + developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi")) + ) + } + + object annotation extends InnerPublishModule{ + + this.artifactNameParts + } + object plugin extends InnerPublishModule{ + def moduleDeps = Seq(annotation) + def ivyDeps = T{ + if (scalaVersion().startsWith("2.")) Agg(ivy"org.scala-lang:scala-compiler:${scalaVersion()}") + else Agg(ivy"org.scala-lang:scala3-compiler_3:${scalaVersion()}") + } + } + + object testutils extends InnerScalaModule + + val testcases = Seq( + "classMethod", + "objectMethod", + "traitMethod", + "genericMethod", + "curriedMethod", + "methodWithImplicit", + "primaryConstructor", + "secondaryConstructor", + "caseclass" + ) + + + object tests extends Cross[Tests](testcases) + + trait Tests extends Cross.Module[String]{ + override def millSourcePath = super.millSourcePath / crossValue + + // Different versions of Unrolled.scala + object v3 extends Unrolled { + def mimaPreviousArtifacts = Seq(v1.jar(), v2.jar()) + } + object v2 extends Unrolled { + def mimaPreviousArtifacts = Seq(v1.jar()) + } + object v1 extends Unrolled{ + def mimaPreviousArtifacts = Seq[PathRef]() + } + + // proxy modules used to make sure old versions of UnrolledTestMain.scala can + // successfully call newer versions of the Unrolled.scala + trait ComparativeScalaModule extends InnerScalaModule{ + def mainClass = Some("unroll.UnrollTestMain") + } + + object v2v3 extends ComparativeScalaModule{ + def unmanagedClasspath = Agg(v2.test.jar(), v3.jar(), testutils.jar()) + } + object v1v3 extends ComparativeScalaModule{ + def unmanagedClasspath = Agg(v1.test.jar(), v3.jar(), testutils.jar()) + } + object v1v2 extends ComparativeScalaModule{ + def unmanagedClasspath = Agg(v1.test.jar(), v2.jar(), testutils.jar()) + } + + trait Unrolled extends InnerScalaModule with LocalMimaModule { + override def run(args: Task[Args] = T.task(Args())) = T.command{/*donothing*/} + object test extends InnerScalaModule{ + def moduleDeps = Seq(Unrolled.this, testutils) + } + + def moduleDeps = Seq(annotation) + override def scalacPluginClasspath = T{ Agg(plugin.jar()) } + +// override def scalaCompilerClasspath = T{ +// super.scalaCompilerClasspath().filter(!_.toString().contains("scala-compiler")) ++ +// Agg(PathRef(os.Path("/Users/lihaoyi/.ivy2/local/org.scala-lang/scala-compiler/2.13.12-bin-SNAPSHOT/jars/scala-compiler.jar"))) +// } + override def scalacOptions = T{ + Seq( + s"-Xplugin:${plugin.jar().path}", + "-Xplugin-require:unroll", + //"-Xprint:all", + //"-Xprint:typer", + //"-Xprint:unroll", + //"-Xprint:patmat", + //"-Xprint:superaccessors" + ) + } + } + + def moduleDeps = Seq(v3) + } +} + +// Fork of the Mima trait from `mill-mima`, to allow us to run MIMA against +// two compilation outputs for testing purposes. +trait LocalMimaModule extends ScalaModule{ + + def mimaWorkerClasspath: T[Agg[PathRef]] = T { + Lib + .resolveDependencies( + repositoriesTask(), + Agg( + ivy"com.github.lolgab:mill-mima-worker-impl_2.13:${MimaBuildInfo.publishVersion}" + .exclude("com.github.lolgab" -> "mill-mima-worker-api_2.13") + ).map(Lib.depToBoundDep(_, mill.main.BuildInfo.scalaVersion)), + ctx = Some(T.log) + ) + } + + def mimaWorker2: Task[worker.api.MimaWorkerApi] = T.task { + val cp = mimaWorkerClasspath() + MimaWorkerExternalModule.mimaWorker().impl(cp) + } + def mimaCurrentArtifact: T[PathRef] = jar() + def mimaPreviousArtifacts: T[Seq[PathRef]] + def mimaReportBinaryIssues(): Command[Unit] = T.command { + val logDebug: java.util.function.Consumer[String] = T.log.debug(_) + val logError: java.util.function.Consumer[String] = T.log.error(_) + val logPrintln: java.util.function.Consumer[String] = + T.log.outputStream.println(_) + val runClasspathIO = + runClasspath().view.map(_.path).filter(os.exists).map(_.toIO).toArray + val current = mimaCurrentArtifact().path.pipe { + case p if os.exists(p) => p + case _ => (T.dest / "emptyClasses").tap(os.makeDir) + }.toIO + + val previous = mimaPreviousArtifacts().iterator.map { + case artifact => + new worker.api.Artifact(artifact.path.toString, artifact.path.toIO) + }.toArray + + val checkDirection = worker.api.CheckDirection.Backward + + val errorOpt: java.util.Optional[String] = mimaWorker2().reportBinaryIssues( + scalaVersion() match{ + case s"2.$x.$y" => s"2.$x" + case s"3.$x.$y" => s"3" + }, + logDebug, + logError, + logPrintln, + checkDirection, + runClasspathIO, + previous, + current, + Array(), + new java.util.HashMap(), + new java.util.HashMap(), + Array(), + "dev" + ) + + if (errorOpt.isPresent()) mill.api.Result.Failure(errorOpt.get()) + else mill.api.Result.Success(()) + } +} + diff --git a/mill b/mill new file mode 100755 index 0000000..f280687 --- /dev/null +++ b/mill @@ -0,0 +1,67 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with MILL_VERSION env variable +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION=0.11.6 +fi + +if [ -z "$MILL_VERSION" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(head -n 1 .config/mill-version 2> /dev/null)" + elif [ -f "mill" ] && [ "$0" != "mill" ] ; then + MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) + else + MILL_VERSION=$DEFAULT_MILL_VERSION + fi +fi + +if [ "x${XDG_CACHE_HOME}" != "x" ] ; then + MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" +else + MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" +fi +MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" + +version_remainder="$MILL_VERSION" +MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" +MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" + +if [ ! -s "$MILL_EXEC_PATH" ] ; then + mkdir -p "$MILL_DOWNLOAD_PATH" + if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then + ASSEMBLY="-assembly" + fi + DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download + MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + MILL_DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/$MILL_VERSION/mill-dist-$MILL_VERSION.jar" + curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" + chmod +x "$DOWNLOAD_FILE" + mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" + unset DOWNLOAD_FILE + unset MILL_DOWNLOAD_URL +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" + + # first arg is a long flag for "--interactive" or starts with "-i" +if [ "$1" = "--bsp" ] || [ "${1%"-i"*}" != "$1" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_VERSION + +exec $MILL_EXEC_PATH $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" diff --git a/unroll/annotation/src/Unroll.scala b/unroll/annotation/src/Unroll.scala new file mode 100644 index 0000000..695948d --- /dev/null +++ b/unroll/annotation/src/Unroll.scala @@ -0,0 +1,3 @@ +package unroll + +class Unroll extends scala.annotation.StaticAnnotation diff --git a/unroll/plugin/resources/plugin.properties b/unroll/plugin/resources/plugin.properties new file mode 100644 index 0000000..978c438 --- /dev/null +++ b/unroll/plugin/resources/plugin.properties @@ -0,0 +1 @@ +pluginClass=unroll.UnrollPluginScala3 \ No newline at end of file diff --git a/unroll/plugin/resources/scalac-plugin.xml b/unroll/plugin/resources/scalac-plugin.xml new file mode 100644 index 0000000..151791a --- /dev/null +++ b/unroll/plugin/resources/scalac-plugin.xml @@ -0,0 +1,4 @@ + + unroll + unroll.UnrollPluginScala2 + \ No newline at end of file diff --git a/unroll/plugin/src-2/UnrollPhaseScala2.scala b/unroll/plugin/src-2/UnrollPhaseScala2.scala new file mode 100644 index 0000000..9976447 --- /dev/null +++ b/unroll/plugin/src-2/UnrollPhaseScala2.scala @@ -0,0 +1,205 @@ +package unroll + +import scala.tools.nsc.symtab.Flags +import scala.tools.nsc.transform.{Transform, TypingTransformers} +import scala.tools.nsc.{Global, Phase} +import tools.nsc.plugins.PluginComponent + +class UnrollPhaseScala2(val global: Global) extends PluginComponent with TypingTransformers with Transform { + + import global._ + + val runsAfter = List("typer") + + override val runsBefore = List("patmat") + + val phaseName = "unroll" + + override def newTransformer(unit: global.CompilationUnit): global.Transformer = { + new UnrollTransformer(unit) + } + + def findUnrollAnnotation(params: Seq[Symbol]): Int = { + params.toList.indexWhere(_.annotations.exists(_.tpe =:= typeOf[unroll.Unroll])) + } + + def copyValDef(vd: ValDef) = { + val newMods = vd.mods.copy(flags = vd.mods.flags ^ Flags.DEFAULTPARAM) + newStrictTreeCopier.ValDef(vd, newMods, vd.name, vd.tpt, EmptyTree) + .setSymbol(vd.symbol) + } + + def copySymbol(owner: Symbol, s: Symbol) = { + val newSymbol = owner.newValueParameter(s.name.toTermName) + newSymbol.setInfo(s.tpe) + newSymbol + } + + implicit class Setter[T <: Tree](t: T){ + def set(s: Symbol) = t.setType(s.tpe).setSymbol(s) + } + def generateSingleForwarder(implDef: ImplDef, + defdef: DefDef, + paramIndex: Int, + firstParamList: List[ValDef], + otherParamLists: List[List[ValDef]]) = { + val forwarderDefSymbol = defdef.symbol.owner.newMethod(defdef.name) + val symbolReplacements = defdef + .vparamss + .flatten + .map { p => p.symbol -> copySymbol(forwarderDefSymbol, p.symbol) } + .toMap ++ { + defdef.symbol.tpe match{ + case MethodType(originalParams, result) => Nil + case PolyType(tparams, MethodType(originalParams, result)) => + // Not sure why this is necessary, but the `originalParams` here + // is different from `defdef.vparamss`, and we need both + originalParams.map(p => (p, copySymbol(forwarderDefSymbol, p))) + } + } + + val forwarderMethodType = defdef.symbol.tpe match{ + case MethodType(originalParams, result) => + val forwarderParams = originalParams.map(symbolReplacements) + MethodType(forwarderParams.take(paramIndex), result) + + case PolyType(tparams, MethodType(originalParams, result)) => + val forwarderParams = originalParams.map(symbolReplacements) + PolyType(tparams, MethodType(forwarderParams.take(paramIndex), result)) + } + + forwarderDefSymbol.setInfo(forwarderMethodType) + + val newVParamss = + List(firstParamList.take(paramIndex).map(copyValDef)) ++ otherParamLists.map(_.map(copyValDef)) + + val forwardedValueParams = firstParamList.take(paramIndex).map(p => Ident(p.name).set(p.symbol)) + + val defaultCalls = for (p <- Range(paramIndex, firstParamList.size)) yield { + val mangledName = defdef.name.toString + "$default$" + (p + 1) + + val defaultOwner = + if (defdef.symbol.isConstructor) implDef.symbol.companionModule + else implDef.symbol + + val defaultMember = defaultOwner.tpe.member(TermName(scala.reflect.NameTransformer.encode(mangledName))) + Ident(mangledName).setSymbol(defaultMember).set(defaultMember) + } + + val forwarderThis = This(defdef.symbol.owner).set(defdef.symbol.owner) + + val forwarderInner = + if (defdef.symbol.isConstructor) Super(forwarderThis, typeNames.EMPTY).set(defdef.symbol.owner) + else forwarderThis + + val nestedForwarderMethodTypes = Seq + .iterate(defdef.symbol.tpe, defdef.vparamss.length + 1){ + case MethodType(args, res) => res + case PolyType(tparams, MethodType(args, res)) => res + } + .drop(1) + + val forwarderCallArgs = + Seq(forwardedValueParams ++ defaultCalls) ++ + newVParamss.tail.map(_.map( p => Ident(p.name).set(p.symbol))) + + val forwarderCall0 = forwarderCallArgs + .zip(nestedForwarderMethodTypes) + .foldLeft(Select(forwarderInner, defdef.name).set(defdef.symbol): Tree){ + case (lhs, (ps, methodType)) => Apply(fun = lhs, args = ps).setType(methodType) + } + + val forwarderCall = + if (!defdef.symbol.isConstructor) forwarderCall0 + else Block(List(forwarderCall0), Literal(Constant(())).setType(typeOf[Unit])) + + val forwarderDef = treeCopy.DefDef( + defdef, + mods = defdef.mods, + name = defdef.name, + tparams = defdef.tparams, + vparamss = newVParamss, + tpt = defdef.tpt, + rhs = forwarderCall + ).set(forwarderDefSymbol) + + // No idea why this `if` guard is necessary, but without it we end + if (defdef.symbol.owner.isTrait) { + defdef.symbol.owner.info.asInstanceOf[ClassInfoType].decls.enter(forwarderDefSymbol) + } + + val (fromSyms, toSyms) = symbolReplacements.toList.unzip + forwarderDef.substituteSymbols(fromSyms, toSyms).asInstanceOf[DefDef] + } + + def generateDefForwarders(implDef: ImplDef, defdef: DefDef, startParamIndex: Int) = defdef.vparamss match { + case Nil => Nil + case firstParamList :: otherParamLists => + for (paramIndex <- Range(startParamIndex, firstParamList.length).toList) yield { + generateSingleForwarder(implDef, defdef, paramIndex, firstParamList, otherParamLists) + } + } + + + class UnrollTransformer(unit: global.CompilationUnit) extends TypingTransformer(unit) { + def generateDefForwarders2(implDef: ImplDef): List[List[DefDef]] = { + implDef.impl.body.collect{ case defdef: DefDef => + + val annotated = + if (defdef.symbol.isCaseCopy && defdef.symbol.name.toString == "copy") defdef.symbol.owner.primaryConstructor + else if (defdef.symbol.isCaseApplyOrUnapply && defdef.symbol.name.toString == "apply") defdef.symbol.owner.companionClass.primaryConstructor + else defdef.symbol + + annotated.asMethod.paramss.take(1).flatMap{ firstParams => + findUnrollAnnotation(firstParams) match { + case -1 => Nil + case n => generateDefForwarders(implDef, defdef, n) + } + } + } + } + + override def transform(tree: global.Tree): global.Tree = { + tree match{ + case md: ModuleDef => + val allNewMethods = generateDefForwarders2(md).flatten + + val classInfoType = md.symbol.moduleClass.info.asInstanceOf[ClassInfoType] + val newClassInfoType = classInfoType.copy(decls = newScopeWith(allNewMethods.map(_.symbol) ++ classInfoType.decls:_*)) + + md.symbol.moduleClass.setInfo(newClassInfoType) + super.transform( + treeCopy.ModuleDef( + md, + mods = md.mods, + name = md.name, + impl = treeCopy.Template( + md.impl, + parents = md.impl.parents, + self = md.impl.self, + body = md.impl.body ++ allNewMethods + ) + ) + ) + case cd: ClassDef => + val allNewMethods = generateDefForwarders2(cd).flatten + super.transform( + treeCopy.ClassDef( + cd, + mods = cd.mods, + name = cd.name, + tparams = cd.tparams, + impl = treeCopy.Template( + cd.impl, + parents = cd.impl.parents, + self = cd.impl.self, + body = cd.impl.body ++ allNewMethods + ) + ) + ) + case _ => super.transform(tree) + } + } + } +} + diff --git a/unroll/plugin/src-2/UnrollPluginScala2.scala b/unroll/plugin/src-2/UnrollPluginScala2.scala new file mode 100644 index 0000000..dd34105 --- /dev/null +++ b/unroll/plugin/src-2/UnrollPluginScala2.scala @@ -0,0 +1,9 @@ +package unroll + +import tools.nsc.Global + +class UnrollPluginScala2(val global: Global) extends tools.nsc.plugins.Plugin { + val name = "unroll" + val description = "Plugin to unroll default methods for binary compatibility" + val components = List(new UnrollPhaseScala2(this.global)) +} \ No newline at end of file diff --git a/unroll/plugin/src-3/UnrollPhaseScala3.scala b/unroll/plugin/src-3/UnrollPhaseScala3.scala new file mode 100644 index 0000000..0a52592 --- /dev/null +++ b/unroll/plugin/src-3/UnrollPhaseScala3.scala @@ -0,0 +1,172 @@ +package unroll + +import dotty.tools.dotc.* +import plugins.* +import core.* +import Contexts.* +import Symbols.* +import Flags.* +import SymDenotations.* +import Decorators.* +import ast.Trees.* +import ast.tpd +import StdNames.nme +import Names.* +import Constants.Constant +import dotty.tools.dotc.core.NameKinds.DefaultGetterName +import dotty.tools.dotc.core.Types.{MethodType, NamedType, PolyType, Type} +import dotty.tools.dotc.core.Symbols + +import scala.language.implicitConversions + +class UnrollPhaseScala3() extends PluginPhase { + import tpd._ + + val phaseName = "unroll" + + override val runsAfter = Set(transform.Pickler.name) + + def copyParam(p: ValDef, parent: Symbol)(using Context) = { + implicitly[Context].typeAssigner.assignType( + cpy.ValDef(p)(p.name, p.tpt, p.rhs), + Symbols.newSymbol(parent, p.name, p.symbol.flags, p.symbol.info) + ) + } + + def copyParam2(p: TypeDef, parent: Symbol)(using Context) = { + implicitly[Context].typeAssigner.assignType( + cpy.TypeDef(p)(p.name, p.rhs), + Symbols.newSymbol(parent, p.name, p.symbol.flags, p.symbol.info) + ) + } + + def generateSingleForwarder(defdef: DefDef, + prevMethodType: Type, + paramLists: List[ParamClause], + firstValueParamClauseIndex: Int, + paramIndex: Int, + isCaseApply: Boolean)(using Context) = { + + def truncateMethodType0(tpe: Type): Type = { + tpe match{ + case pt: PolyType => PolyType(pt.paramInfos, truncateMethodType0(pt.resType)) + case mt: MethodType => MethodType(mt.paramInfos.take(paramIndex), mt.resType) + } + } + + val truncatedMethodType = truncateMethodType0(prevMethodType) + + val forwarderDefSymbol = Symbols.newSymbol( + defdef.symbol.owner, + defdef.name, + defdef.symbol.flags, + truncatedMethodType + ) + + val updated: List[ParamClause] = paramLists.zipWithIndex.map{ case (ps, i) => + if (i == firstValueParamClauseIndex) ps.take(paramIndex).map(p => copyParam(p.asInstanceOf[ValDef], forwarderDefSymbol)) + else { + if (ps.headOption.exists(_.isInstanceOf[TypeDef])) ps.map(p => copyParam2(p.asInstanceOf[TypeDef], forwarderDefSymbol)) + else ps.map(p => copyParam(p.asInstanceOf[ValDef], forwarderDefSymbol)) + } + } + + val defaultCalls = for (n <- Range(paramIndex, paramLists(firstValueParamClauseIndex).size)) yield { + if (defdef.symbol.isConstructor) { + ref(defdef.symbol.owner.companionModule) + .select(DefaultGetterName(defdef.name, n)) + } else if (isCaseApply) { + ref(defdef.symbol.owner.companionModule) + .select(DefaultGetterName(termName(""), n)) + } else { + This(defdef.symbol.owner.asClass) + .select(DefaultGetterName(defdef.name, n)) + } + } + + val allNewParamTrees = + updated.zipWithIndex.map{case (ps, i) => + if (i == firstValueParamClauseIndex) ps.map(p => ref(p.symbol)) ++ defaultCalls + else ps.map(p => ref(p.symbol)) + } + + val forwarderInner: Tree = This(defdef.symbol.owner.asClass).select(defdef.symbol) + + val forwarderCall0 = allNewParamTrees.foldLeft[Tree](forwarderInner){ + case (lhs: Tree, newParams) => + if (newParams.headOption.exists(_.isInstanceOf[TypeTree])) TypeApply(lhs, newParams) + else Apply(lhs, newParams) + } + + val forwarderCall = + if (!defdef.symbol.isConstructor) forwarderCall0 + else Block(List(forwarderCall0), Literal(Constant(()))) + + val newDefDef = implicitly[Context].typeAssigner.assignType( + cpy.DefDef(defdef)( + name = forwarderDefSymbol.name, + paramss = updated, + tpt = defdef.tpt, + rhs = forwarderCall + ), + forwarderDefSymbol + ) + + newDefDef + } + + def generateDefForwarders(defdef: DefDef)(using Context): Seq[DefDef] = { + import dotty.tools.dotc.core.NameOps.isConstructorName + + val isCaseCopy = + defdef.name.toString == "copy" && defdef.symbol.owner.is(CaseClass) + + val isCaseApply = + defdef.name.toString == "apply" && defdef.symbol.owner.companionClass.is(CaseClass) + + val annotated = + if (isCaseCopy) defdef.symbol.owner.primaryConstructor + else if (isCaseApply) defdef.symbol.owner.companionClass.primaryConstructor + else defdef.symbol + + val firstValueParamClauseIndex = defdef.paramss.indexWhere(!_.headOption.exists(_.isInstanceOf[TypeDef])) + + if (firstValueParamClauseIndex == -1) Nil + else { + annotated + .paramSymss(firstValueParamClauseIndex) + .indexWhere(_.annotations.exists(_.symbol.fullName.toString == "unroll.Unroll")) match{ + case -1 => Nil + case startParamIndex => + val prevMethodType = defdef.symbol.info + for (paramIndex <- Range(startParamIndex, defdef.paramss(firstValueParamClauseIndex).size)) yield { + generateSingleForwarder( + defdef, + prevMethodType, + defdef.paramss, + firstValueParamClauseIndex, + paramIndex, + isCaseApply + ) + } + } + } + + } + override def transformTemplate(tmpl: tpd.Template)(using Context): tpd.Tree = { + + def potentialDefDefs = (tmpl.body ++ Seq(tmpl.constr)).collect{ case defdef: DefDef if defdef.paramss.nonEmpty => defdef } + + val newMethods = potentialDefDefs.map(generateDefForwarders) + + super.transformTemplate( + cpy.Template(tmpl)( + tmpl.constr, + tmpl.parents, + tmpl.derived, + tmpl.self, + tmpl.body ++ newMethods.flatten + ) + ) + } +} diff --git a/unroll/plugin/src-3/UnrollPluginScala3.scala b/unroll/plugin/src-3/UnrollPluginScala3.scala new file mode 100644 index 0000000..5137317 --- /dev/null +++ b/unroll/plugin/src-3/UnrollPluginScala3.scala @@ -0,0 +1,9 @@ +package unroll + +import dotty.tools.dotc.plugins._ + +class UnrollPluginScala3 extends StandardPlugin { + val name: String = "unroll" + override val description: String = "Count method calls" + def init(options: List[String]): List[PluginPhase] = List(new UnrollPhaseScala3()) +} \ No newline at end of file diff --git a/unroll/tests/caseclass/src/UnrollTestMain.scala b/unroll/tests/caseclass/src/UnrollTestMain.scala new file mode 100644 index 0000000..b59cbcb --- /dev/null +++ b/unroll/tests/caseclass/src/UnrollTestMain.scala @@ -0,0 +1,47 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getConstructor(classOf[String])).isFailure) + println() + assert( + cls.getConstructor(classOf[String], classOf[Int]) + .newInstance("hello", 2: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2true0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE) + .asInstanceOf[Unrolled] + .foo == + "hello2false0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2false3" + ) + + cls.getConstructors.foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/caseclass/v1/src/Unrolled.scala b/unroll/tests/caseclass/v1/src/Unrolled.scala new file mode 100644 index 0000000..997ae2f --- /dev/null +++ b/unroll/tests/caseclass/v1/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +case class Unrolled(s: String, n: Int = 1){ + def foo = s + n +} diff --git a/unroll/tests/caseclass/v1/test/src/UnrollTestMain.scala b/unroll/tests/caseclass/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..4e6100e --- /dev/null +++ b/unroll/tests/caseclass/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,36 @@ +package unroll +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2") + + logAssertStartsWith(Unrolled("cow").foo, "cow1") + logAssertStartsWith(Unrolled("cow", 2).foo, "cow2") + + val unrolled = Unrolled("cow") + + logAssertStartsWith(unrolled.copy(s = "cow").foo, "cow1") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2).foo, "cow2") + + val Unrolled(s, n) = unrolled + + assert(s == "cow") + assert(n == 1) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/caseclass/v2/src/Unrolled.scala b/unroll/tests/caseclass/v2/src/Unrolled.scala new file mode 100644 index 0000000..f5ab8cb --- /dev/null +++ b/unroll/tests/caseclass/v2/src/Unrolled.scala @@ -0,0 +1,6 @@ +package unroll + +case class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true){ + def foo = s + n + b +} + diff --git a/unroll/tests/caseclass/v2/test/src/UnrollTestMain.scala b/unroll/tests/caseclass/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..9b401fb --- /dev/null +++ b/unroll/tests/caseclass/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,41 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false") + + logAssertStartsWith(Unrolled("cow").foo, "cow1true") + logAssertStartsWith(Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(Unrolled("cow", 2, false).foo, "cow2false") + + val unrolled = Unrolled("cow") + + logAssertStartsWith(unrolled.copy(s = "cow").foo, "cow1true") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2).foo, "cow2true") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2, b = false).foo, "cow2false") + + val Unrolled(s, n, b) = unrolled + + assert(s == "cow") + assert(n == 1) + assert(b == true) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/caseclass/v3/src/Unrolled.scala b/unroll/tests/caseclass/v3/src/Unrolled.scala new file mode 100644 index 0000000..70998c4 --- /dev/null +++ b/unroll/tests/caseclass/v3/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +case class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0){ + def foo = s + n + b + l +} diff --git a/unroll/tests/caseclass/v3/test/src/UnrollTestMain.scala b/unroll/tests/caseclass/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..70c86cb --- /dev/null +++ b/unroll/tests/caseclass/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,45 @@ +package unroll +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(new Unrolled("cow", 2, false, 9L).foo, "cow2false9") + + logAssertStartsWith(Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(Unrolled("cow", 2, false, 9L).foo, "cow2false9") + + val unrolled = Unrolled("cow") + + logAssertStartsWith(unrolled.copy(s = "cow").foo, "cow1true0") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2).foo, "cow2true0") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2, b = false).foo, "cow2false0") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2, b = false, l = 9L).foo, "cow2false9") + + val Unrolled(s, n, b, l) = unrolled + + assert(s == "cow") + assert(n == 1) + assert(b == true) + assert(l == 0L) + + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/classMethod/src/UnrollTestMain.scala b/unroll/tests/classMethod/src/UnrollTestMain.scala new file mode 100644 index 0000000..36ce4bc --- /dev/null +++ b/unroll/tests/classMethod/src/UnrollTestMain.scala @@ -0,0 +1,44 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String]).invoke(instance, "hello") == + "hello1true0" + ) + + assert( + cls.getMethod("foo", classOf[String], classOf[Int]).invoke(instance, "hello", 2: Integer) == + "hello2true0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/classMethod/v1/src/Unrolled.scala b/unroll/tests/classMethod/v1/src/Unrolled.scala new file mode 100644 index 0000000..cf23188 --- /dev/null +++ b/unroll/tests/classMethod/v1/src/Unrolled.scala @@ -0,0 +1,6 @@ +package unroll + +class Unrolled{ + def foo(s: String) = s +} + diff --git a/unroll/tests/classMethod/v1/test/src/UnrollTestMain.scala b/unroll/tests/classMethod/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..a1c0e34 --- /dev/null +++ b/unroll/tests/classMethod/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,23 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/classMethod/v2/src/Unrolled.scala b/unroll/tests/classMethod/v2/src/Unrolled.scala new file mode 100644 index 0000000..1f37888 --- /dev/null +++ b/unroll/tests/classMethod/v2/src/Unrolled.scala @@ -0,0 +1,6 @@ +package unroll + +class Unrolled{ + def foo(s: String, @Unroll n: Int = 1, b: Boolean = true) = s + n + b +} + diff --git a/unroll/tests/classMethod/v2/test/src/UnrollTestMain.scala b/unroll/tests/classMethod/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..787f073 --- /dev/null +++ b/unroll/tests/classMethod/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,25 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/classMethod/v3/src/Unrolled.scala b/unroll/tests/classMethod/v3/src/Unrolled.scala new file mode 100644 index 0000000..d62b694 --- /dev/null +++ b/unroll/tests/classMethod/v3/src/Unrolled.scala @@ -0,0 +1,11 @@ +package unroll + +class Unrolled{ + def foo(s: String, @Unroll n: Int = 1, b: Boolean = true, l: Long = 0) = s + n + b + l +} + + + + + + diff --git a/unroll/tests/classMethod/v3/test/src/UnrollTestMain.scala b/unroll/tests/classMethod/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..251a0c9 --- /dev/null +++ b/unroll/tests/classMethod/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,26 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3), "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/curriedMethod/src/UnrollTestMain.scala b/unroll/tests/curriedMethod/src/UnrollTestMain.scala new file mode 100644 index 0000000..4de8c0b --- /dev/null +++ b/unroll/tests/curriedMethod/src/UnrollTestMain.scala @@ -0,0 +1,44 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String], classOf[String => String]).invoke(instance, "hello", identity[String](_)) == + "hello1true0" + ) + + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[String => String]).invoke(instance, "hello", 2: Integer, identity[String](_)) == + "hello2true0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, identity[String](_)) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer, identity[String](_)) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/curriedMethod/v1/src/Unrolled.scala b/unroll/tests/curriedMethod/v1/src/Unrolled.scala new file mode 100644 index 0000000..e508d43 --- /dev/null +++ b/unroll/tests/curriedMethod/v1/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +class Unrolled{ + def foo(s: String)(f: String => String) = f(s) +} diff --git a/unroll/tests/curriedMethod/v1/test/src/UnrollTestMain.scala b/unroll/tests/curriedMethod/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..6482053 --- /dev/null +++ b/unroll/tests/curriedMethod/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,23 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow")(identity), "cow") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/curriedMethod/v2/src/Unrolled.scala b/unroll/tests/curriedMethod/v2/src/Unrolled.scala new file mode 100644 index 0000000..f6d6428 --- /dev/null +++ b/unroll/tests/curriedMethod/v2/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +class Unrolled{ + def foo(s: String, @Unroll n: Int = 1, b: Boolean = true)(f: String => String) = f(s + n + b) +} diff --git a/unroll/tests/curriedMethod/v2/test/src/UnrollTestMain.scala b/unroll/tests/curriedMethod/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..0310f80 --- /dev/null +++ b/unroll/tests/curriedMethod/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,25 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow")(identity), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2)(identity), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false)(identity), "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/curriedMethod/v3/src/Unrolled.scala b/unroll/tests/curriedMethod/v3/src/Unrolled.scala new file mode 100644 index 0000000..8173923 --- /dev/null +++ b/unroll/tests/curriedMethod/v3/src/Unrolled.scala @@ -0,0 +1,8 @@ +package unroll + +class Unrolled{ + def foo(s: String, @Unroll n: Int = 1, b: Boolean = true, l: Long = 0)(f: String => String) = f(s + n + b + l) +} + + + diff --git a/unroll/tests/curriedMethod/v3/test/src/UnrollTestMain.scala b/unroll/tests/curriedMethod/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..e7d89b3 --- /dev/null +++ b/unroll/tests/curriedMethod/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,26 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow")(identity), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2)(identity), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false)(identity), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3)(identity), "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/genericMethod/src/UnrollTestMain.scala b/unroll/tests/genericMethod/src/UnrollTestMain.scala new file mode 100644 index 0000000..36ce4bc --- /dev/null +++ b/unroll/tests/genericMethod/src/UnrollTestMain.scala @@ -0,0 +1,44 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String]).invoke(instance, "hello") == + "hello1true0" + ) + + assert( + cls.getMethod("foo", classOf[String], classOf[Int]).invoke(instance, "hello", 2: Integer) == + "hello2true0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/genericMethod/v1/src/Unrolled.scala b/unroll/tests/genericMethod/v1/src/Unrolled.scala new file mode 100644 index 0000000..a41354b --- /dev/null +++ b/unroll/tests/genericMethod/v1/src/Unrolled.scala @@ -0,0 +1,6 @@ +package unroll + +class Unrolled{ + def foo[T](s: T) = s.toString +} + diff --git a/unroll/tests/genericMethod/v1/test/src/UnrollTestMain.scala b/unroll/tests/genericMethod/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..a1c0e34 --- /dev/null +++ b/unroll/tests/genericMethod/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,23 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/genericMethod/v2/src/Unrolled.scala b/unroll/tests/genericMethod/v2/src/Unrolled.scala new file mode 100644 index 0000000..cf1c0da --- /dev/null +++ b/unroll/tests/genericMethod/v2/src/Unrolled.scala @@ -0,0 +1,6 @@ +package unroll + +class Unrolled{ + def foo[T](s: T, @Unroll n: Int = 1, b: Boolean = true) = s.toString + n + b +} + diff --git a/unroll/tests/genericMethod/v2/test/src/UnrollTestMain.scala b/unroll/tests/genericMethod/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..787f073 --- /dev/null +++ b/unroll/tests/genericMethod/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,25 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/genericMethod/v3/src/Unrolled.scala b/unroll/tests/genericMethod/v3/src/Unrolled.scala new file mode 100644 index 0000000..5486816 --- /dev/null +++ b/unroll/tests/genericMethod/v3/src/Unrolled.scala @@ -0,0 +1,11 @@ +package unroll + +class Unrolled{ + def foo[T](s: T, @Unroll n: Int = 1, b: Boolean = true, l: Long = 0) = s.toString + n + b + l +} + + + + + + diff --git a/unroll/tests/genericMethod/v3/test/src/UnrollTestMain.scala b/unroll/tests/genericMethod/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..251a0c9 --- /dev/null +++ b/unroll/tests/genericMethod/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,26 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3), "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/methodWithImplicit/src/UnrollTestMain.scala b/unroll/tests/methodWithImplicit/src/UnrollTestMain.scala new file mode 100644 index 0000000..4de8c0b --- /dev/null +++ b/unroll/tests/methodWithImplicit/src/UnrollTestMain.scala @@ -0,0 +1,44 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String], classOf[String => String]).invoke(instance, "hello", identity[String](_)) == + "hello1true0" + ) + + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[String => String]).invoke(instance, "hello", 2: Integer, identity[String](_)) == + "hello2true0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, identity[String](_)) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer, identity[String](_)) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/methodWithImplicit/v1/src/Unrolled.scala b/unroll/tests/methodWithImplicit/v1/src/Unrolled.scala new file mode 100644 index 0000000..4413748 --- /dev/null +++ b/unroll/tests/methodWithImplicit/v1/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +class Unrolled{ + def foo(s: String)(implicit f: String => String) = f(s) +} diff --git a/unroll/tests/methodWithImplicit/v1/test/src/UnrollTestMain.scala b/unroll/tests/methodWithImplicit/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..50d3005 --- /dev/null +++ b/unroll/tests/methodWithImplicit/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,24 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + implicit def f(s: String): String = s + logAssertStartsWith(new Unrolled().foo("cow"), "cow") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/methodWithImplicit/v2/src/Unrolled.scala b/unroll/tests/methodWithImplicit/v2/src/Unrolled.scala new file mode 100644 index 0000000..a6bbe44 --- /dev/null +++ b/unroll/tests/methodWithImplicit/v2/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +class Unrolled{ + def foo(s: String, @Unroll n: Int = 1, b: Boolean = true)(implicit f: String => String) = f(s + n + b) +} diff --git a/unroll/tests/methodWithImplicit/v2/test/src/UnrollTestMain.scala b/unroll/tests/methodWithImplicit/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..526e409 --- /dev/null +++ b/unroll/tests/methodWithImplicit/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,26 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + implicit def f(s: String): String = s + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/methodWithImplicit/v3/src/Unrolled.scala b/unroll/tests/methodWithImplicit/v3/src/Unrolled.scala new file mode 100644 index 0000000..8da2fb3 --- /dev/null +++ b/unroll/tests/methodWithImplicit/v3/src/Unrolled.scala @@ -0,0 +1,8 @@ +package unroll + +class Unrolled{ + def foo(s: String, @Unroll n: Int = 1, b: Boolean = true, l: Long = 0)(implicit f: String => String) = f(s + n + b + l) +} + + + diff --git a/unroll/tests/methodWithImplicit/v3/test/src/UnrollTestMain.scala b/unroll/tests/methodWithImplicit/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..b29fcf2 --- /dev/null +++ b/unroll/tests/methodWithImplicit/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,27 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + implicit def f(s: String): String = s + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3), "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/objectMethod/src/UnrollTestMain.scala b/unroll/tests/objectMethod/src/UnrollTestMain.scala new file mode 100644 index 0000000..01567c6 --- /dev/null +++ b/unroll/tests/objectMethod/src/UnrollTestMain.scala @@ -0,0 +1,62 @@ +package unroll + + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val instance = Unrolled + val instanceCls = Class.forName("unroll.Unrolled$") + + + instanceCls.getMethods.filter(_.getName.contains("foo")).foreach(println) + + // Make sure singleton instance forwarder methods are generated + assert(scala.util.Try(instanceCls.getMethod("foo", classOf[String])).isFailure) + assert( + instanceCls.getMethod("foo", classOf[String], classOf[Int]).invoke(instance, "hello", 2: Integer) == + "hello2true0" + ) + assert( + instanceCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + instanceCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + // Make sure static forwarder methods are generated + val staticCls = Class.forName("unroll.Unrolled") + staticCls.getMethods.filter(_.getName.contains("foo")).foreach(println) + + assert(scala.util.Try(staticCls.getMethod("foo", classOf[String])).isFailure) + assert( + staticCls.getMethod("foo", classOf[String], classOf[Int]).invoke(null, "hello", 2: Integer) == + "hello2true0" + ) + assert( + staticCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(null, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + staticCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(null, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + } +} + + + + + + + + + + + + + diff --git a/unroll/tests/objectMethod/v1/src/Unrolled.scala b/unroll/tests/objectMethod/v1/src/Unrolled.scala new file mode 100644 index 0000000..3553383 --- /dev/null +++ b/unroll/tests/objectMethod/v1/src/Unrolled.scala @@ -0,0 +1,8 @@ +package unroll + +object Unrolled{ + + def foo(s: String, n: Int = 1) = s + n +} + + diff --git a/unroll/tests/objectMethod/v1/test/src/UnrollTestMain.scala b/unroll/tests/objectMethod/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..d2eb363 --- /dev/null +++ b/unroll/tests/objectMethod/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,24 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(Unrolled.foo("cow"), "cow1") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/objectMethod/v2/src/Unrolled.scala b/unroll/tests/objectMethod/v2/src/Unrolled.scala new file mode 100644 index 0000000..61dd2ec --- /dev/null +++ b/unroll/tests/objectMethod/v2/src/Unrolled.scala @@ -0,0 +1,8 @@ +package unroll + +object Unrolled{ + def foo(s: String, n: Int = 1, @Unroll b: Boolean = true) = s + n + b +} + + + diff --git a/unroll/tests/objectMethod/v2/test/src/UnrollTestMain.scala b/unroll/tests/objectMethod/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..c653761 --- /dev/null +++ b/unroll/tests/objectMethod/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,25 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(Unrolled.foo("cow"), "cow1true") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/objectMethod/v3/src/Unrolled.scala b/unroll/tests/objectMethod/v3/src/Unrolled.scala new file mode 100644 index 0000000..4d427e4 --- /dev/null +++ b/unroll/tests/objectMethod/v3/src/Unrolled.scala @@ -0,0 +1,65 @@ +package unroll + +object Unrolled{ + def foo(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0) = s + n + b + l +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unroll/tests/objectMethod/v3/test/src/UnrollTestMain.scala b/unroll/tests/objectMethod/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..9a1e035 --- /dev/null +++ b/unroll/tests/objectMethod/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,26 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(Unrolled.foo("cow"), "cow1true0") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true0") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false0") + logAssertStartsWith(Unrolled.foo("cow", 2, false, 3), "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/primaryConstructor/src/UnrollTestMain.scala b/unroll/tests/primaryConstructor/src/UnrollTestMain.scala new file mode 100644 index 0000000..b59cbcb --- /dev/null +++ b/unroll/tests/primaryConstructor/src/UnrollTestMain.scala @@ -0,0 +1,47 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getConstructor(classOf[String])).isFailure) + println() + assert( + cls.getConstructor(classOf[String], classOf[Int]) + .newInstance("hello", 2: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2true0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE) + .asInstanceOf[Unrolled] + .foo == + "hello2false0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2false3" + ) + + cls.getConstructors.foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/primaryConstructor/v1/src/Unrolled.scala b/unroll/tests/primaryConstructor/v1/src/Unrolled.scala new file mode 100644 index 0000000..c7574f4 --- /dev/null +++ b/unroll/tests/primaryConstructor/v1/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +class Unrolled(s: String, n: Int = 1){ + def foo = s + n +} diff --git a/unroll/tests/primaryConstructor/v1/test/src/UnrollTestMain.scala b/unroll/tests/primaryConstructor/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..725678f --- /dev/null +++ b/unroll/tests/primaryConstructor/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,24 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/primaryConstructor/v2/src/Unrolled.scala b/unroll/tests/primaryConstructor/v2/src/Unrolled.scala new file mode 100644 index 0000000..cdefcaf --- /dev/null +++ b/unroll/tests/primaryConstructor/v2/src/Unrolled.scala @@ -0,0 +1,5 @@ +package unroll + +class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true){ + def foo = s + n + b +} diff --git a/unroll/tests/primaryConstructor/v2/test/src/UnrollTestMain.scala b/unroll/tests/primaryConstructor/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..0464bf9 --- /dev/null +++ b/unroll/tests/primaryConstructor/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,25 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/primaryConstructor/v3/src/Unrolled.scala b/unroll/tests/primaryConstructor/v3/src/Unrolled.scala new file mode 100644 index 0000000..4e33b91 --- /dev/null +++ b/unroll/tests/primaryConstructor/v3/src/Unrolled.scala @@ -0,0 +1,12 @@ +package unroll + +class Unrolled(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0){ + def foo = s + n + b + l +} + + + + + + + diff --git a/unroll/tests/primaryConstructor/v3/test/src/UnrollTestMain.scala b/unroll/tests/primaryConstructor/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..6c668ba --- /dev/null +++ b/unroll/tests/primaryConstructor/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,26 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(new Unrolled("cow", 2, false, 3).foo, "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/secondaryConstructor/src/UnrollTestMain.scala b/unroll/tests/secondaryConstructor/src/UnrollTestMain.scala new file mode 100644 index 0000000..b59cbcb --- /dev/null +++ b/unroll/tests/secondaryConstructor/src/UnrollTestMain.scala @@ -0,0 +1,47 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getConstructor(classOf[String])).isFailure) + println() + assert( + cls.getConstructor(classOf[String], classOf[Int]) + .newInstance("hello", 2: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2true0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE) + .asInstanceOf[Unrolled] + .foo == + "hello2false0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2false3" + ) + + cls.getConstructors.foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/secondaryConstructor/v1/src/Unrolled.scala b/unroll/tests/secondaryConstructor/v1/src/Unrolled.scala new file mode 100644 index 0000000..529a3fc --- /dev/null +++ b/unroll/tests/secondaryConstructor/v1/src/Unrolled.scala @@ -0,0 +1,9 @@ +package unroll + +class Unrolled(){ + var foo = "" + def this(s: String, n: Int = 1) = { + this() + foo = s + n + } +} diff --git a/unroll/tests/secondaryConstructor/v1/test/src/UnrollTestMain.scala b/unroll/tests/secondaryConstructor/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..725678f --- /dev/null +++ b/unroll/tests/secondaryConstructor/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,24 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/secondaryConstructor/v2/src/Unrolled.scala b/unroll/tests/secondaryConstructor/v2/src/Unrolled.scala new file mode 100644 index 0000000..ed0abf3 --- /dev/null +++ b/unroll/tests/secondaryConstructor/v2/src/Unrolled.scala @@ -0,0 +1,10 @@ +package unroll + +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @Unroll b: Boolean = true) = { + this() + foo = s + n + b + } +} diff --git a/unroll/tests/secondaryConstructor/v2/test/src/UnrollTestMain.scala b/unroll/tests/secondaryConstructor/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..0464bf9 --- /dev/null +++ b/unroll/tests/secondaryConstructor/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,25 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/secondaryConstructor/v3/src/Unrolled.scala b/unroll/tests/secondaryConstructor/v3/src/Unrolled.scala new file mode 100644 index 0000000..17c9ed1 --- /dev/null +++ b/unroll/tests/secondaryConstructor/v3/src/Unrolled.scala @@ -0,0 +1,17 @@ +package unroll + +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0) = { + this() + foo = s + n + b + l + } +} + + + + + + + diff --git a/unroll/tests/secondaryConstructor/v3/test/src/UnrollTestMain.scala b/unroll/tests/secondaryConstructor/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..6c668ba --- /dev/null +++ b/unroll/tests/secondaryConstructor/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,26 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(new Unrolled("cow", 2, false, 3).foo, "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/traitMethod/src/UnrollTestMain.scala b/unroll/tests/traitMethod/src/UnrollTestMain.scala new file mode 100644 index 0000000..355b85b --- /dev/null +++ b/unroll/tests/traitMethod/src/UnrollTestMain.scala @@ -0,0 +1,41 @@ +package unroll + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val instance = new Unrolled {} + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getMethod("foo", classOf[String])).isFailure) + println() + assert( + cls.getMethod("foo", classOf[String], classOf[Int]).invoke(instance, "hello", 2: Integer) == + "hello2true0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).foreach(println) + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/traitMethod/v1/src/Unrolled.scala b/unroll/tests/traitMethod/v1/src/Unrolled.scala new file mode 100644 index 0000000..eaadde7 --- /dev/null +++ b/unroll/tests/traitMethod/v1/src/Unrolled.scala @@ -0,0 +1,7 @@ +package unroll + +trait Unrolled{ + def foo(s: String, n: Int = 1) = s + n +} + +object Unrolled extends Unrolled \ No newline at end of file diff --git a/unroll/tests/traitMethod/v1/test/src/UnrollTestMain.scala b/unroll/tests/traitMethod/v1/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..604443f --- /dev/null +++ b/unroll/tests/traitMethod/v1/test/src/UnrollTestMain.scala @@ -0,0 +1,14 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val unrolled = new Unrolled{} + logAssertStartsWith(unrolled.foo("cow"), "cow1") + logAssertStartsWith(unrolled.foo("cow", 2), "cow2") + + logAssertStartsWith(Unrolled.foo("cow"), "cow1") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2") + } +} diff --git a/unroll/tests/traitMethod/v2/src/Unrolled.scala b/unroll/tests/traitMethod/v2/src/Unrolled.scala new file mode 100644 index 0000000..0ce4bcd --- /dev/null +++ b/unroll/tests/traitMethod/v2/src/Unrolled.scala @@ -0,0 +1,7 @@ +package unroll + +trait Unrolled{ + def foo(s: String, n: Int = 1, @Unroll b: Boolean = true) = s + n + b +} + +object Unrolled extends Unrolled \ No newline at end of file diff --git a/unroll/tests/traitMethod/v2/test/src/UnrollTestMain.scala b/unroll/tests/traitMethod/v2/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..a1c41f6 --- /dev/null +++ b/unroll/tests/traitMethod/v2/test/src/UnrollTestMain.scala @@ -0,0 +1,30 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val unrolled = new Unrolled{} + logAssertStartsWith(unrolled.foo("cow"), "cow1true") + logAssertStartsWith(unrolled.foo("cow", 2), "cow2true") + logAssertStartsWith(unrolled.foo("cow", 2, false), "cow2false") + + logAssertStartsWith(Unrolled.foo("cow"), "cow1true") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false") + } +} + + + + + + + + + + + + + + diff --git a/unroll/tests/traitMethod/v3/src/Unrolled.scala b/unroll/tests/traitMethod/v3/src/Unrolled.scala new file mode 100644 index 0000000..de2a47b --- /dev/null +++ b/unroll/tests/traitMethod/v3/src/Unrolled.scala @@ -0,0 +1,8 @@ +package unroll + +trait Unrolled{ + def foo(s: String, n: Int = 1, @Unroll b: Boolean = true, l: Long = 0) = s + n + b + l +} + + +object Unrolled extends Unrolled \ No newline at end of file diff --git a/unroll/tests/traitMethod/v3/test/src/UnrollTestMain.scala b/unroll/tests/traitMethod/v3/test/src/UnrollTestMain.scala new file mode 100644 index 0000000..a2dcbdb --- /dev/null +++ b/unroll/tests/traitMethod/v3/test/src/UnrollTestMain.scala @@ -0,0 +1,32 @@ +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMain{ + def main(args: Array[String]): Unit = { + val unrolled = new Unrolled{} + logAssertStartsWith(unrolled.foo("cow"), "cow1true0") + logAssertStartsWith(unrolled.foo("cow", 2), "cow2true0") + logAssertStartsWith(unrolled.foo("cow", 2, false), "cow2false0") + logAssertStartsWith(unrolled.foo("cow", 2, false, 3), "cow2false3") + + logAssertStartsWith(Unrolled.foo("cow"), "cow1true0") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true0") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false0") + logAssertStartsWith(Unrolled.foo("cow", 2, false, 3), "cow2false3") + } +} + + + + + + + + + + + + + + diff --git a/unroll/testutils/src/TestUtils.scala b/unroll/testutils/src/TestUtils.scala new file mode 100644 index 0000000..c729024 --- /dev/null +++ b/unroll/testutils/src/TestUtils.scala @@ -0,0 +1,14 @@ +package unroll + +object TestUtils{ + def logAssertStartsWith[T](t: T, s: String) = { + println(t) + // We use .startsWith for our tests for backwards compatibility testing. + // As "new versions" of the code add more parameters to the class or method, + // the returned string gets longer, but the prefix remains the same. By using + // `.startsWith`, we emulate the real-world scenario where the behavior of a + // class or method changes, but does so in a backwards compatible manner: adding + // new functionality while still obeying all previous invariants + assert(t.toString.startsWith(s)) + } +} \ No newline at end of file