Created
November 14, 2015 08:44
-
-
Save iximeow/b2229d6e49ae3965569d to your computer and use it in GitHub Desktop.
macros to test for does-it-compile-ness
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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