diff --git a/.travis.yml b/.travis.yml index 72c8b29..5d7e4d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ script: - curl -L -o ~/bin/mill https://github.com/lihaoyi/mill/releases/download/0.4.0/0.4.0-12-102ddf && chmod +x ~/bin/mill - curl https://raw.githubusercontent.com/scala-native/scala-native/master/scripts/travis_setup.sh | bash -x - export PATH=~/bin/mill:$PATH - - mill __.test.run + - ./mill __.test.run diff --git a/build.sc b/build.sc index 708588b..44566ef 100644 --- a/build.sc +++ b/build.sc @@ -25,10 +25,12 @@ trait SourcecodeMainModule extends CrossScalaModule { def offset: os.RelPath = os.rel - def compileIvyDeps = Agg( - ivy"org.scala-lang:scala-reflect:${scalaVersion()}", - ivy"org.scala-lang:scala-compiler:${scalaVersion()}" - ) + def compileIvyDeps = + if (crossScalaVersion.startsWith("2")) Agg( + ivy"org.scala-lang:scala-reflect:${crossScalaVersion}", + ivy"org.scala-lang:scala-compiler:${crossScalaVersion}" + ) + else Agg.empty[Dep] def sources = T.sources( super.sources() @@ -62,7 +64,7 @@ trait SourcecodeTestModule extends ScalaModule { } object sourcecode extends Module { - object jvm extends Cross[JvmSourcecodeModule]("2.11.12", "2.12.8", "2.13.0") + object jvm extends Cross[JvmSourcecodeModule]("2.11.12", "2.12.8", "2.13.0", "0.21.0-bin-20191125-a64725c-NIGHTLY") class JvmSourcecodeModule(val crossScalaVersion: String) extends SourcecodeMainModule with ScalaModule with SourcecodeModule { @@ -71,6 +73,15 @@ object sourcecode extends Module { def moduleDeps = Seq(JvmSourcecodeModule.this) val crossScalaVersion = JvmSourcecodeModule.this.crossScalaVersion } + + override def docJar = + if (crossScalaVersion.startsWith("2")) super.docJar + else T { + val outDir = T.ctx().dest + val javadocDir = outDir / 'javadoc + os.makeDir.all(javadocDir) + mill.api.Result.Success(mill.modules.Jvm.createJar(Agg(javadocDir))(outDir)) + } } object js extends Cross[JsSourcecodeModule]( diff --git a/mill b/mill new file mode 100755 index 0000000..e969003 --- /dev/null +++ b/mill @@ -0,0 +1,37 @@ +#!/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 +DEFAULT_MILL_VERSION=0.5.0 + +set -e + +if [ -z "$MILL_VERSION" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" + elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then + MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) + else + MILL_VERSION=$DEFAULT_MILL_VERSION + fi +fi + +MILL_DOWNLOAD_PATH="$HOME/.mill/download" +MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/$MILL_VERSION" + +if [ ! -x "$MILL_EXEC_PATH" ] ; then + mkdir -p $MILL_DOWNLOAD_PATH + DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download + MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION-assembly" + 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 + +unset MILL_DOWNLOAD_PATH +unset MILL_VERSION + +exec $MILL_EXEC_PATH "$@" diff --git a/sourcecode/js/src/test/scala/sourcecode/TestUtil.scala b/sourcecode/js/src/test/scala/sourcecode/TestUtil.scala new file mode 100644 index 0000000..a3f1414 --- /dev/null +++ b/sourcecode/js/src/test/scala/sourcecode/TestUtil.scala @@ -0,0 +1,7 @@ +package sourcecode + +object TestUtil { + + val isDotty = false + +} diff --git a/sourcecode/jvm/src/test/scala/sourcecode/TestUtil.scala b/sourcecode/jvm/src/test/scala/sourcecode/TestUtil.scala new file mode 100644 index 0000000..6949a5c --- /dev/null +++ b/sourcecode/jvm/src/test/scala/sourcecode/TestUtil.scala @@ -0,0 +1,17 @@ +package sourcecode + +object TestUtil { + + // FIXME In dotty, scala.util.Properties.versionNumberString is still like 2.12.x + lazy val isDotty = { + val cl: ClassLoader = Thread.currentThread().getContextClassLoader + try { + cl.loadClass("dotty.DottyPredef") + true + } catch { + case _: ClassNotFoundException => + false + } + } + +} diff --git a/sourcecode/native/src/test/scala/sourcecode/TestUtil.scala b/sourcecode/native/src/test/scala/sourcecode/TestUtil.scala new file mode 100644 index 0000000..a3f1414 --- /dev/null +++ b/sourcecode/native/src/test/scala/sourcecode/TestUtil.scala @@ -0,0 +1,7 @@ +package sourcecode + +object TestUtil { + + val isDotty = false + +} diff --git a/sourcecode/src-0/sourcecode/Macros.scala b/sourcecode/src-0/sourcecode/Macros.scala new file mode 100644 index 0000000..4b5fe68 --- /dev/null +++ b/sourcecode/src-0/sourcecode/Macros.scala @@ -0,0 +1,254 @@ +package sourcecode + +import scala.language.implicitConversions +import scala.quoted._ +import scala.tasty.Reflection + +trait NameMacros { + inline implicit def generate: Name = + ${ Macros.nameImpl } +} + +trait NameMachineMacros { + inline implicit def generate: Name.Machine = + ${ Macros.nameMachineImpl } +} + +trait FullNameMacros { + inline implicit def generate: FullName = + ${ Macros.fullNameImpl } +} + +trait FullNameMachineMacros { + inline implicit def generate: FullName.Machine = + ${ Macros.fullNameMachineImpl } +} + +trait FileMacros { + inline implicit def generate: sourcecode.File = + ${ Macros.fileImpl } +} + +trait FileNameMacros { + inline implicit def generate: sourcecode.FileName = + ${ Macros.fileNameImpl } +} + +trait LineMacros { + inline implicit def generate: sourcecode.Line = + ${ Macros.lineImpl } +} + +trait EnclosingMacros { + inline implicit def generate: Enclosing = + ${ Macros.enclosingImpl } +} + +trait EnclosingMachineMacros { + inline implicit def generate: Enclosing.Machine = + ${ Macros.enclosingMachineImpl } +} + +trait PkgMacros { + inline implicit def generate: Pkg = + ${ Macros.pkgImpl } +} + +trait TextMacros { + inline implicit def generate[T](v: => T): Text[T] = ${ Macros.text('v) } + inline def apply[T](v: => T): Text[T] = ${ Macros.text('v) } +} + +trait ArgsMacros { + inline implicit def generate: Args = + ${ Macros.argsImpl } +} + +object Util{ + def isSynthetic(c: Reflection)(s: c.Symbol) = isSyntheticName(getName(c)(s)) + def isSyntheticName(name: String) = { + name == "" || (name.startsWith("")) + } + def getName(c: Reflection)(s: c.Symbol) = { + import c.given + s.name.trim + .stripSuffix("$") // meh + } +} + +object Macros { + + def actualOwner(c: Reflection)(owner: c.Symbol): c.Symbol = { + import c.given + var owner0 = owner + // second condition is meh + while(Util.isSynthetic(c)(owner0) || Util.getName(c)(owner0) == "ev") { + owner0 = owner0.owner + } + owner0 + } + + def nameImpl(given ctx: QuoteContext): Expr[Name] = { + import ctx.tasty.given + val owner = actualOwner(ctx.tasty)(ctx.tasty.rootContext.owner) + val simpleName = Util.getName(ctx.tasty)(owner) + '{Name(${simpleName.toExpr})} + } + + private def adjustName(s: String): String = + // Required to get the same name from dotty + if (s.startsWith("")) + s.stripSuffix("$>") + ">" + else + s + + def nameMachineImpl(given ctx: QuoteContext): Expr[Name.Machine] = { + import ctx.tasty.given + val owner = ctx.tasty.rootContext.owner + val simpleName = adjustName(Util.getName(ctx.tasty)(owner)) + '{Name.Machine(${simpleName.toExpr})} + } + + def fullNameImpl(given ctx: QuoteContext): Expr[FullName] = { + import ctx.tasty.given + @annotation.tailrec def cleanChunk(chunk: String): String = + val refined = chunk.stripPrefix("_$").stripSuffix("$") + if chunk != refined then cleanChunk(refined) else refined + + val owner = actualOwner(ctx.tasty)(ctx.tasty.rootContext.owner) + val fullName = + owner.fullName.trim + .split("\\.", -1) + .filterNot(Util.isSyntheticName) + .map(cleanChunk) + .mkString(".") + '{FullName(${fullName.toExpr})} + } + + def fullNameMachineImpl(given ctx: QuoteContext): Expr[FullName.Machine] = { + import ctx.tasty.given + val owner = ctx.tasty.rootContext.owner + val fullName = owner.fullName.trim + .split("\\.", -1) + .map(_.stripPrefix("_$").stripSuffix("$")) // meh + .map(adjustName) + .mkString(".") + '{FullName.Machine(${fullName.toExpr})} + } + + def fileImpl(given ctx: QuoteContext): Expr[sourcecode.File] = { + import ctx.tasty.given + val file = ctx.tasty.rootPosition.sourceFile.jpath.toAbsolutePath.toString + '{sourcecode.File(${file.toExpr})} + } + + def fileNameImpl(given ctx: QuoteContext): Expr[sourcecode.FileName] = { + import ctx.tasty.given + val name = ctx.tasty.rootPosition.sourceFile.jpath.getFileName.toString + '{sourcecode.FileName(${Expr(name)})} + } + + def lineImpl(given ctx: QuoteContext): Expr[sourcecode.Line] = { + import ctx.tasty.given + val line = ctx.tasty.rootPosition.startLine + 1 + '{sourcecode.Line(${line.toExpr})} + } + + def enclosingImpl(given ctx: QuoteContext): Expr[Enclosing] = { + val path = enclosing(ctx.tasty)( + !Util.isSynthetic(ctx.tasty)(_) + ) + + '{Enclosing(${path.toExpr})} + } + + def enclosingMachineImpl(given ctx: QuoteContext): Expr[Enclosing.Machine] = { + val path = enclosing(ctx.tasty, machine = true)(_ => true) + '{Enclosing.Machine(${path.toExpr})} + } + + def pkgImpl(given ctx: QuoteContext): Expr[Pkg] = { + import ctx.tasty.given + val path = enclosing(ctx.tasty) { + case s if s.isPackageDef => true + case _ => false + } + + '{Pkg(${path.toExpr})} + } + + def argsImpl(given ctx: QuoteContext): Expr[Args] = { + import ctx.tasty.{ _, given } + + val param: List[List[ctx.tasty.ValDef]] = { + def nearestEnclosingMethod(owner: ctx.tasty.Symbol): List[List[ctx.tasty.ValDef]] = + owner match { + case defSym if defSym.isDefDef => + defSym.tree.asInstanceOf[DefDef].paramss + case classSym if classSym.isClassDef => + classSym.tree.asInstanceOf[ClassDef].constructor.paramss + case _ => + nearestEnclosingMethod(owner.owner) + } + + nearestEnclosingMethod(ctx.tasty.rootContext.owner) + } + + val texts0 = param.map(_.foldRight('{List.empty[Text[_]]}) { + case (vd @ ValDef(nme, _, optV), l) => + '{Text(${optV.fold('None)(_.seal)}, ${nme.toExpr}) :: $l} + }) + val texts = texts0.foldRight('{List.empty[List[Text[_]]]}) { + case (l, acc) => + '{$l :: $acc} + } + + '{Args($texts)} + } + + + def text[T: Type](v: Expr[T])(given ctx: QuoteContext): Expr[sourcecode.Text[T]] = { + import ctx.tasty.given + val txt = v.unseal.pos.sourceCode + '{sourcecode.Text[T]($v, ${txt.toExpr})} + } + + sealed trait Chunk + object Chunk{ + case class PkgObj(name: String) extends Chunk + case class ClsTrt(name: String) extends Chunk + case class ValVarLzyDef(name: String) extends Chunk + + } + + def enclosing(c: Reflection, machine: Boolean = false)(filter: c.Symbol => Boolean): String = { + import c.{ _, given } + + var current = c.rootContext.owner + if (!machine) + current = actualOwner(c)(current) + var path = List.empty[Chunk] + while(current != Symbol.noSymbol && current != defn.RootPackage && current != defn.RootClass){ + if (filter(current)) { + + val chunk = current match { + case sym if + sym.isValDef || sym.isDefDef => Chunk.ValVarLzyDef + case sym if + sym.isPackageDef || + sym.moduleClass != Symbol.noSymbol => Chunk.PkgObj + case sym if sym.isClassDef => Chunk.ClsTrt + case _ => Chunk.PkgObj + } + + path = chunk(Util.getName(c)(current).stripSuffix("$")) :: path + } + current = current.owner + } + path.map{ + case Chunk.PkgObj(s) => adjustName(s) + "." + case Chunk.ClsTrt(s) => adjustName(s) + "#" + case Chunk.ValVarLzyDef(s) => adjustName(s) + " " + }.mkString.dropRight(1) + } +} diff --git a/sourcecode/src/sourcecode/Compat.scala b/sourcecode/src-2/sourcecode/Compat.scala similarity index 100% rename from sourcecode/src/sourcecode/Compat.scala rename to sourcecode/src-2/sourcecode/Compat.scala diff --git a/sourcecode/src/sourcecode/Macros.scala b/sourcecode/src-2/sourcecode/Macros.scala similarity index 100% rename from sourcecode/src/sourcecode/Macros.scala rename to sourcecode/src-2/sourcecode/Macros.scala diff --git a/sourcecode/test/src/sourcecode/Apply.scala b/sourcecode/test/src/sourcecode/Apply.scala index a3cca87..013c519 100644 --- a/sourcecode/test/src/sourcecode/Apply.scala +++ b/sourcecode/test/src/sourcecode/Apply.scala @@ -42,8 +42,8 @@ object Apply { val enclosing = sourcecode.Enclosing() assert( - (enclosing == "sourcecode.Apply.applyRun myLazy$lzy Bar#enclosing") || - (enclosing == "sourcecode.Apply.applyRun myLazy Bar#enclosing") // encoding changed in Scala 2.12 + enclosing == "sourcecode.Apply.applyRun myLazy$lzy Bar#enclosing" || + enclosing == "sourcecode.Apply.applyRun myLazy Bar#enclosing" // encoding changed in Scala 2.12 ) } val b = new Bar{} diff --git a/sourcecode/test/src/sourcecode/ArgsTests.scala b/sourcecode/test/src/sourcecode/ArgsTests.scala index 31a28a3..0d353ca 100644 --- a/sourcecode/test/src/sourcecode/ArgsTests.scala +++ b/sourcecode/test/src/sourcecode/ArgsTests.scala @@ -7,6 +7,15 @@ object ArgsTests { def debug(implicit arguments: sourcecode.Args): Unit = args = arguments.value.map(_.map(t => t.source -> t.value)) + // FIXME Can't manage to get the arg values from dotty… + val checkValues = !TestUtil.isDotty + + def check(expected: Seq[Seq[(String, Any)]]): Unit = + if (checkValues) + assert(args == expected, s"Expected: $expected, got: $args") + else + assert(args.map(_.map(_._1)) == expected.map(_.map(_._1)), s"Expected: ${expected.map(_.map(_._1))}, got: ${args.map(_.map(_._1))}") + def foo(p1: String, p2: Long, p3: Boolean)(foo: String, bar: String): Unit = { debug } @@ -36,25 +45,25 @@ object ArgsTests { } new Foo("text", 42, false)("foo", "bar") - assert(args == Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo", "bar" -> "bar"))) + check(Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo", "bar" -> "bar"))) new Foo("text", 42) - assert(args == Seq(Seq("p1" -> "text", "p2" -> 42))) + check(Seq(Seq("p1" -> "text", "p2" -> 42))) foo("text", 42, false)("foo", "bar") - assert(args == Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo", "bar" -> "bar"))) + check(Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo", "bar" -> "bar"))) bar("text", 42, false)("foo", "bar") - assert(args == Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo", "bar" -> "bar"))) + check(Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo", "bar" -> "bar"))) baz - assert(args == Seq()) + check(Seq()) withImplicit("text", 42, false)("foo") - assert(args == Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo"))) + check(Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "foo"))) implicit val implicitFoo = "bar" withImplicit("text", 42, false) - assert(args == Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "bar"))) + check(Seq(Seq("p1" -> "text", "p2" -> 42, "p3" -> false), Seq("foo" -> "bar"))) } } diff --git a/sourcecode/test/src/sourcecode/Implicits.scala b/sourcecode/test/src/sourcecode/Implicits.scala index f637a1f..2e96519 100644 --- a/sourcecode/test/src/sourcecode/Implicits.scala +++ b/sourcecode/test/src/sourcecode/Implicits.scala @@ -29,7 +29,10 @@ object Implicits { assert(name.value == "name") val fullName = implicitly[sourcecode.FullName] - assert(fullName.value == "sourcecode.Implicits.Bar.fullName") + assert( + fullName.value == "sourcecode.Implicits.Bar.fullName" || + fullName.value == "sourcecode.Implicits._$Bar.fullName" // Dotty + ) val file = implicitly[sourcecode.File] assert(file.value.endsWith("/sourcecode/Implicits.scala")) @@ -38,12 +41,13 @@ object Implicits { assert(fileName.value == "Implicits.scala") val line = implicitly[sourcecode.Line] - assert(line.value == 40) + assert(line.value == 43) val enclosing = implicitly[sourcecode.Enclosing] assert( - (enclosing.value == "sourcecode.Implicits.implicitRun myLazy$lzy Bar#enclosing") || - (enclosing.value == "sourcecode.Implicits.implicitRun myLazy Bar#enclosing") // encoding changed in Scala 2.12 + enclosing.value == "sourcecode.Implicits.implicitRun myLazy$lzy Bar#enclosing" || + enclosing.value == "sourcecode.Implicits.implicitRun myLazy Bar#enclosing" || // encoding changed in Scala 2.12 + enclosing.value == "sourcecode.Implicits.implicitRun myLazy Bar.enclosing" // Dotty ) } val b = new Bar{} diff --git a/sourcecode/test/src/sourcecode/TextTests.scala b/sourcecode/test/src/sourcecode/TextTests.scala index 2500120..a92d6c6 100644 --- a/sourcecode/test/src/sourcecode/TextTests.scala +++ b/sourcecode/test/src/sourcecode/TextTests.scala @@ -5,8 +5,13 @@ object TextTests { assert(foo(1) == (1, "1")) val bar = Seq("lols") assert(foo(bar) == (Seq("lols"), "bar")) - assert(foo('lol.toString * 2) == ("'lol'lol", "'lol.toString * 2")) - assert(foo{println("Hello"); 'lol.toString * 2} == ("'lol'lol", "'lol.toString * 2")) + if (TestUtil.isDotty) { + assert(foo(Symbol("lol").toString * 2) == ("'lol'lol", "Symbol(\"lol\").toString * 2")) + assert(foo{println("Hello"); Symbol("lol").toString * 2} == ("'lol'lol", "Symbol(\"lol\").toString * 2")) + } else { + assert(foo(Symbol("lol").toString * 2) == ("'lol'lol", "Symbol(\"lol\").toString * 2")) + assert(foo{println("Hello"); Symbol("lol").toString * 2} == ("'lol'lol", "Symbol(\"lol\").toString * 2")) + } } def foo[T](v: sourcecode.Text[T]) = (v.value, v.source) }