Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mix Scala 2 macros in MUnit Scala 3 module #283

Merged
merged 8 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,7 @@ inThisBuild(
testFrameworks := List(
new TestFramework("munit.Framework")
),
useSuperShell := false,
scalacOptions ++= List(
"-target:jvm-1.8",
"-language:implicitConversions"
)
useSuperShell := false
)
)

Expand All @@ -65,6 +61,10 @@ val scala3Versions = scala3Stable :: scala3Previous
val allScalaVersions = scala2Versions ++ scala3Versions
def isNotScala211(v: Option[(Long, Long)]): Boolean = !v.contains((2, 11))
def isScala2(v: Option[(Long, Long)]): Boolean = v.exists(_._1 == 2)
val isScala3Setting = Def.setting {
isScala3(CrossVersion.partialVersion(scalaVersion.value))
}

def isScala3(v: Option[(Long, Long)]): Boolean = v.exists(_._1 != 2)
val isScalaJS = Def.setting[Boolean](
SettingKey[Boolean]("scalaJSUseMainModuleInitializer").?.value.isDefined
Expand Down Expand Up @@ -129,9 +129,13 @@ val sharedSettings = List(
List(
"-Yrangepos",
"-Xexperimental",
"-Ywarn-unused-import"
"-Ywarn-unused-import",
"-target:jvm-1.8"
)
case Some((major, _)) if major != 2 =>
List(
"-language:implicitConversions"
)
case Some((major, _)) if major != 2 => List()
case _ =>
List(
"-target:jvm-1.8",
Expand Down Expand Up @@ -187,15 +191,12 @@ lazy val munit = crossProject(JSPlatform, JVMPlatform, NativePlatform)
}
result.toList
},
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((major, _)) if major != 2 => Nil
case _ =>
List(
"org.scala-lang" % "scala-reflect" % scalaVersion.value
)
}
}
libraryDependencies ++= List(
"org.scala-lang" % "scala-reflect" % {
if (isScala3Setting.value) scala213
else scalaVersion.value
} % Provided
)
)
.nativeConfigure(sharedNativeConfigure)
.nativeSettings(
Expand Down
92 changes: 9 additions & 83 deletions munit/shared/src/main/scala-2/munit/internal/MacroCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,102 +4,28 @@ import munit.Clue
import munit.Location
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
import scala.reflect.macros.TypecheckException
import scala.reflect.macros.ParseException

object MacroCompat {

trait LocationMacro {
implicit def generate: Location = macro locationImpl
implicit def generate: Location = macro MacroCompatScala2.locationImpl
}

def locationImpl(c: Context): c.Tree = {
import c.universe._
val line = Literal(Constant(c.enclosingPosition.line))
val path = Literal(Constant(c.enclosingPosition.source.path))
New(typeOf[Location], path, line)
}
@deprecated("Use MacroCompatScala2.locationImpl instead", "2020-01-06")
def locationImpl(c: Context): c.Tree = MacroCompatScala2.locationImpl(c)

trait ClueMacro {
implicit def generate[T](value: T): Clue[T] = macro clueImpl
implicit def generate[T](value: T): Clue[T] = macro MacroCompatScala2.clueImpl
}

def clueImpl(c: Context)(value: c.Tree): c.Tree = {
import c.universe._
import compat._
val text: String =
if (value.pos != null && value.pos.isRange) {
val chars = value.pos.source.content
val start = value.pos.start
val end = value.pos.end
if (
end > start &&
start >= 0 && start < chars.length &&
end >= 0 && end < chars.length
) {
new String(chars, start, end - start)
} else {
""
}
} else {
""
}
def simplifyType(tpe: Type): Type = tpe match {
case TypeRef(ThisType(pre), sym, args) if pre == sym.owner =>
simplifyType(TypeRef(NoPrefix, sym, args))
case t =>
// uncomment to debug:
// Printers.log(t)(Location.empty)
t.widen
}
val source = Literal(Constant(text))
val valueType = Literal(Constant(simplifyType(value.tpe).toString()))
New(
TypeRef(NoPrefix, typeOf[Clue[_]].typeSymbol, List(value.tpe.widen)),
source,
value,
valueType
)
}
@deprecated("Use MacroCompatScala2.clueImpl instead", "2020-01-06")
def clueImpl(c: Context)(value: c.Tree): c.Tree = MacroCompatScala2.clueImpl(c)(value)

trait CompileErrorMacro {
def compileErrors(code: String): String = macro compileErrorsImpl
def compileErrors(code: String): String = macro MacroCompatScala2.compileErrorsImpl
}

def compileErrorsImpl(c: Context)(code: c.Tree): c.Tree = {
import c.universe._
val toParse: String = code match {
case Literal(Constant(literal: String)) => literal
case _ =>
c.abort(
code.pos,
"cannot compile dynamic expressions, only constant literals.\n" +
"To fix this problem, pass in a string literal in double quotes \"...\""
)
}
@deprecated("Use MacroCompatScala2.compileErrorsImpl instead", "2020-01-06")
def compileErrorsImpl(c: Context)(value: c.Tree): c.Tree = MacroCompatScala2.compileErrorsImpl(c)(value)

def formatError(message: String, pos: scala.reflect.api.Position): String =
new StringBuilder()
.append("error:")
.append(if (message.contains('\n')) "\n" else " ")
.append(message)
.append("\n")
.append(pos.lineContent)
.append("\n")
.append(" " * (pos.column - 1))
.append("^")
.toString()

val message: String =
try {
c.typecheck(c.parse(s"{\n$toParse\n}"))
""
} catch {
case e: ParseException =>
formatError(e.getMessage(), e.pos)
case e: TypecheckException =>
formatError(e.getMessage(), e.pos)
}
Literal(Constant(message))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package munit.internal
import munit.Clue
import munit.Location
import scala.quoted._
import scala.language.experimental.macros

object MacroCompat {

trait LocationMacro {
inline implicit def generate: Location = ${ locationImpl() }
implicit def generate: Location = macro MacroCompatScala2.locationImpl
}

def locationImpl()(using Quotes): Expr[Location] = {
Expand All @@ -20,6 +22,7 @@ object MacroCompat {

trait ClueMacro {
inline implicit def generate[T](value: T): Clue[T] = ${ clueImpl('value) }
implicit def generate[T](value: T): Clue[T] = macro MacroCompatScala2.clueImpl
}

def clueImpl[T: Type](value: Expr[T])(using Quotes): Expr[Clue[T]] = {
Expand All @@ -42,6 +45,8 @@ object MacroCompat {
s"error:${separator}${trimMessage}\n${error.lineContent}\n${indent}^"
}.mkString("\n")
}
def compileErrors(code: String): String = macro MacroCompatScala2.compileErrorsImpl
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package munit.internal
import munit.Clue
import munit.Location
import scala.quoted._
import scala.language.experimental.macros

object MacroCompat {

trait LocationMacro {
inline implicit def generate: Location = ${ locationImpl() }
implicit def generate: Location = macro MacroCompatScala2.locationImpl
}

def locationImpl()(using Quotes): Expr[Location] = {
Expand All @@ -20,6 +22,7 @@ object MacroCompat {

trait ClueMacro {
inline implicit def generate[T](value: T): Clue[T] = ${ clueImpl('value) }
implicit def generate[T](value: T): Clue[T] = macro MacroCompatScala2.clueImpl
}

def clueImpl[T: Type](value: Expr[T])(using Quotes): Expr[Clue[T]] = {
Expand All @@ -29,6 +32,7 @@ object MacroCompat {
'{ new Clue(${Expr(source)}, $value, ${Expr(valueType)}) }
}


trait CompileErrorMacro {
inline def compileErrors(inline code: String): String = {
val errors = scala.compiletime.testing.typeCheckErrors(code)
Expand All @@ -42,6 +46,7 @@ object MacroCompat {
s"error:${separator}${trimMessage}\n${error.lineContent}\n${indent}^"
}.mkString("\n")
}
def compileErrors(code: String): String = macro MacroCompatScala2.compileErrorsImpl
}

}
95 changes: 95 additions & 0 deletions munit/shared/src/main/scala/munit/internal/MacroCompatScala2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package munit.internal

import munit.Clue
import munit.Location
import scala.reflect.macros.blackbox.Context
import scala.reflect.macros.TypecheckException
import scala.reflect.macros.ParseException

object MacroCompatScala2 {

def locationImpl(c: Context): c.Tree = {
import c.universe._
val line = Literal(Constant(c.enclosingPosition.line))
val path = Literal(Constant(c.enclosingPosition.source.path))
New(c.mirror.staticClass(classOf[Location].getName()), path, line)
}

def clueImpl(c: Context)(value: c.Tree): c.Tree = {
import c.universe._
val text: String =
if (value.pos != null && value.pos.isRange) {
val chars = value.pos.source.content
val start = value.pos.start
val end = value.pos.end
if (
end > start &&
start >= 0 && start < chars.length &&
end >= 0 && end < chars.length
) {
new String(chars, start, end - start)
} else {
""
}
} else {
""
}
def simplifyType(tpe: Type): Type = tpe match {
case TypeRef(ThisType(pre), sym, args) if pre == sym.owner =>
simplifyType(c.internal.typeRef(NoPrefix, sym, args))
case t =>
// uncomment to debug:
// Printers.log(t)(Location.empty)
t.widen
}
val source = Literal(Constant(text))
val valueType = Literal(Constant(simplifyType(value.tpe).toString()))
New(
c.internal.typeRef(
NoPrefix,
c.mirror.staticClass(classOf[Clue[_]].getName()),
List(value.tpe.widen)
),
source,
value,
valueType
)
}

def compileErrorsImpl(c: Context)(code: c.Tree): c.Tree = {
import c.universe._
val toParse: String = code match {
case Literal(Constant(literal: String)) => literal
case _ =>
c.abort(
code.pos,
"cannot compile dynamic expressions, only constant literals.\n" +
"To fix this problem, pass in a string literal in double quotes \"...\""
)
}

def formatError(message: String, pos: scala.reflect.api.Position): String =
new StringBuilder()
.append("error:")
.append(if (message.contains('\n')) "\n" else " ")
.append(message)
.append("\n")
.append(pos.lineContent)
.append("\n")
.append(" " * (pos.column - 1))
.append("^")
.toString()

val message: String =
try {
c.typecheck(c.parse(s"{\n$toParse\n}"))
""
} catch {
case e: ParseException =>
formatError(e.getMessage(), e.pos)
case e: TypecheckException =>
formatError(e.getMessage(), e.pos)
}
Literal(Constant(message))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ object Printers {
var i = 0
while (i < string.length()) {
val ch = string.charAt(i)
(ch: @switch) match {
ch match {
case '"' | '\'' => out.append(ch)
case _ => printChar(ch, out, isEscapeUnicode = false)
}
Expand Down
Loading