Skip to content

Instantly share code, notes, and snippets.

@iximeow
Created November 14, 2015 08:44
Show Gist options
  • Select an option

  • Save iximeow/b2229d6e49ae3965569d to your computer and use it in GitHub Desktop.

Select an option

Save iximeow/b2229d6e49ae3965569d to your computer and use it in GitHub Desktop.
macros to test for does-it-compile-ness
import scala.reflect.macros.{Context, TypecheckException}
import scala.reflect.runtime.universe._
import scala.language.experimental.macros
import scala.util.{Success, Failure}
import java.util.regex.Pattern
/*
* Heavily inspired by ShouldNotTypecheck (from slick) and
* illTyped (from shapeless:
* https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/test/typechecking.scala
* )
*
*/
object CompileChecks {
def illTyped(code: String): Boolean = macro _isIllTyped
def illTypedWith(code: String, expected: String): Boolean = macro _isIllTypedWith
def wellTyped(code: String): Boolean = macro _isWellTyped
def mustBeIllTyped(code: String): Unit = macro _mustBeIllTyped
def mustBeIllTypedWith(code: String, expected: String): Unit = macro _mustBeIllTypedWith
def mustBeWellTyped(code: String): Unit = macro _mustBeWellTyped
def _mustBeIllTyped(ctx: Context)(code: ctx.Expr[String]): ctx.Expr[Unit] = {
_typechecks(ctx)(code) match {
case Right(_) =>
ctx.abort(ctx.enclosingPosition, "Type-checking succeeded unexpectedly; expected an error")
case Left(errMsg) => ctx.universe.reify(())
}
}
def _mustBeIllTypedWith(ctx: Context)(code: ctx.Expr[String], expected: ctx.Expr[String]): ctx.Expr[Unit] = {
import ctx.universe._
val expPat = expected match {
case Expr(Literal(Constant(patStr: String))) =>
Pattern.compile(patStr, Pattern.CASE_INSENSITIVE | Pattern.DOTALL)
case _ =>
ctx.abort(ctx.enclosingPosition, "Can only pattern match type errors against constant strings")
}
_typechecks(ctx)(code) match {
case Left(errMsg) =>
if (expPat.matcher(errMsg).matches) {
ctx.universe.reify(())
} else {
ctx.abort(ctx.enclosingPosition, "Type-checking did not fail as expected. Got:\n" + errMsg + "\nbut expected something matching:\n" + expPat)
}
case Right(_) =>
ctx.abort(ctx.enclosingPosition, "Type-checking did not fail, but it was expected to.")
}
}
def _mustBeWellTyped(ctx: Context)(code: ctx.Expr[String]): ctx.Expr[Unit] = {
_typechecks(ctx)(code) match {
case Right(_) => ctx.universe.reify(())
case Left(errMsg) => ctx.abort(ctx.enclosingPosition, "Type-checking did not succeed. Got error:\n" + errMsg)
}
}
def _isIllTyped(ctx: Context)(code: ctx.Expr[String]): ctx.Expr[Boolean] = {
import ctx.universe._
val res = _typechecks(ctx)(code).isLeft
// to work around free variable ctx error when reify(_typechecks...)
if (res) {
reify(true)
} else {
reify(false)
}
}
def _isWellTyped(ctx: Context)(code: ctx.Expr[String]): ctx.Expr[Boolean] = {
import ctx.universe._
val res = _typechecks(ctx)(code).isRight
// to work around free variable ctx error when reify(_typechecks...)
if (res) {
reify(true)
} else {
reify(false)
}
}
def _isIllTypedWith(ctx: Context)(code: ctx.Expr[String], expected: ctx.Expr[String]): ctx.Expr[Boolean] = {
import ctx.universe._
val expPat = expected match {
case Expr(Literal(Constant(patStr: String))) =>
Pattern.compile(patStr, Pattern.CASE_INSENSITIVE | Pattern.DOTALL)
case _ =>
ctx.abort(ctx.enclosingPosition, "Can only pattern match type errors against constant strings")
}
_typechecks(ctx)(code) match {
case Right(_) => ctx.universe.reify(false)
case Left(errStr) => ctx.universe.reify(expPat.matcher(errStr).matches)
}
}
def _typechecks(ctx: Context)(code: ctx.Expr[String]): Either[String, Unit] = {
import ctx.universe._
val codeStr = code match {
case Expr(Literal(Constant(rawCodeStr: String))) => rawCodeStr
case _ => ctx.abort(ctx.enclosingPosition, "Can only check typed-ness of constant strings of code.")
}
val res = util.Try(ctx.typecheck(ctx.parse("{ "+codeStr+" }"))).map(_ => Right(())) recover {
case(e: TypecheckException) => Left(e.getMessage)
}
res.getOrElse {
ctx.abort(ctx.enclosingPosition, res.failed.get.getMessage)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment