From ada2a8aafaad65ca78d1141ae8be86003c26ab6f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 29 Jan 2018 11:07:13 +0000 Subject: [PATCH] Give SourcePosition a macro instance creator --- build.sbt | 14 ++++-- .../scala/sbt/internal/util/Positions.scala | 46 +++++++++++++++++++ .../internal/util/SourcePositionSpec.scala | 18 ++++++++ 3 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 internal/util-position/src/test/scala/sbt/internal/util/SourcePositionSpec.scala diff --git a/build.sbt b/build.sbt index 5945c0429..cc43f2c35 100644 --- a/build.sbt +++ b/build.sbt @@ -94,11 +94,15 @@ lazy val utilControl = (project in internalPath / "util-control").settings( mimaSettings, ) -val utilPosition = (project in file("internal") / "util-position").settings( - commonSettings, - name := "Util Position", - mimaSettings, -) +val utilPosition = (project in file("internal") / "util-position") + .dependsOn(utilTesting % Test) + .settings( + commonSettings, + name := "Util Position", + scalacOptions += "-language:experimental.macros", + libraryDependencies += scalaReflect.value, + mimaSettings, + ) // logging lazy val utilLogging = (project in internalPath / "util-logging") diff --git a/internal/util-position/src/main/scala/sbt/internal/util/Positions.scala b/internal/util-position/src/main/scala/sbt/internal/util/Positions.scala index a11ae9c24..ca3626db1 100644 --- a/internal/util-position/src/main/scala/sbt/internal/util/Positions.scala +++ b/internal/util-position/src/main/scala/sbt/internal/util/Positions.scala @@ -18,3 +18,49 @@ final case class LineRange(start: Int, end: Int) { final case class RangePosition(path: String, range: LineRange) extends FilePosition { def startLine = range.start } + +object SourcePosition { + + /** Creates a SourcePosition by using the enclosing position of the invocation of this method. + * @see [[scala.reflect.macros.Enclosures#enclosingPosition]] + * @return SourcePosition + */ + def fromEnclosing(): SourcePosition = macro SourcePositionMacro.fromEnclosingImpl + +} + +import scala.annotation.tailrec +import scala.reflect.macros.blackbox +import scala.reflect.internal.util.UndefinedPosition + +final class SourcePositionMacro(val c: blackbox.Context) { + import c.universe.{ NoPosition => _, _ } + + def fromEnclosingImpl(): Expr[SourcePosition] = { + val pos = c.enclosingPosition + if (!pos.isInstanceOf[UndefinedPosition] && pos.line >= 0 && pos.source != null) { + val f = pos.source.file + val name = constant[String](ownerSource(f.path, f.name)) + val line = constant[Int](pos.line) + reify { LinePosition(name.splice, line.splice) } + } else + reify { NoPosition } + } + + private[this] def ownerSource(path: String, name: String): String = { + @tailrec def inEmptyPackage(s: Symbol): Boolean = + s != NoSymbol && ( + s.owner == c.mirror.EmptyPackage + || s.owner == c.mirror.EmptyPackageClass + || inEmptyPackage(s.owner) + ) + + c.internal.enclosingOwner match { + case ec if !ec.isStatic => name + case ec if inEmptyPackage(ec) => path + case ec => s"(${ec.fullName}) $name" + } + } + + private[this] def constant[T: WeakTypeTag](t: T): Expr[T] = c.Expr[T](Literal(Constant(t))) +} diff --git a/internal/util-position/src/test/scala/sbt/internal/util/SourcePositionSpec.scala b/internal/util-position/src/test/scala/sbt/internal/util/SourcePositionSpec.scala new file mode 100644 index 000000000..6fa955171 --- /dev/null +++ b/internal/util-position/src/test/scala/sbt/internal/util/SourcePositionSpec.scala @@ -0,0 +1,18 @@ +package sbt.internal.util + +import org.scalatest._ + +class SourcePositionSpec extends FlatSpec { + "SourcePosition()" should "return a sane SourcePosition" in { + val filename = "SourcePositionSpec.scala" + val lineNumber = 9 + SourcePosition.fromEnclosing() match { + case LinePosition(path, startLine) => assert(path === filename && startLine === lineNumber) + case RangePosition(path, range) => assert(path === filename && inRange(range, lineNumber)) + case NoPosition => fail("No source position found") + } + } + + private def inRange(range: LineRange, lineNo: Int) = + range.start until range.end contains lineNo +}