I often do small experiments with scala and type acrobatics. And I need to check if I implemented expected behaviour precisely. So I write several examples that should either compile or fail.
Those that fail give me a little inconvenience, it stops entire file from compiling. So I commonly use the same pattern: write failing example, check that it really fails, comment it. Then I encounter some example that works unexpectedly, fix bug in the code. After that I need to uncomment failed examples and make sure that they would fail again.
Not so much trouble really from expended time perspective, but a programmer is always irritated from doing monotonous manual work.
So I’m searching for a method to annotate block of code as intended to fail. Either some compiler switch or macros that sends block content to the compiler and check for failing. The goal is simple: compiler should swallow all errors inside the block and emit error there were none.
As I deduced from the provided links, there is no reference implementation for hiding compiler errors, so people invents their own macroses. I tried to write macros too. And I get quite a wonder that I need to get scala-reflect.jar for for using internal language feature.
Ordinary macro methods turned out to be no good: by the time they are called arguments become typechecked so it could not intercept intended error in their arguments.
So I tried to switch to macroparadise annotations, that transforms code before it get typechecked.
final class Fail extends StaticAnnotation {
def macroTransform(annottees : Any*) : Any = macro ShouldFailImpl.impl
}
object ShouldFailImpl {
def impl(ctx : Context)(annottees : ctx.Expr[Any]*) : ctx.Expr[Any] = {
var failed = false
try {
for (code <- annottees) {
val tree = code.tree.duplicate
ctx.typeCheck(tree)
}
} catch {
case e: TypecheckException => failed = true
}
if (! failed)
ctx.error(ctx.enclosingPosition, "expected to fail but typechecks succesfully")
ctx.universe.reify(())
}
}
But suddenly it get me Illegal cyclic reference error. I tried to get clean copy of tree to avoid cycles, but failed nevertheless.
I find writing macroses very painful and obstructed task: I could find almost zero information on how they should interact with compiler context and how it works.
Then I tried to write simple compiler plugin. If I has already macroparadise plugin, why not to insert another one? Information blockade towards compiler plugins is even denser than for macroses. I found pair of introduction videos from which I could easily get that awesome scala compiler is divided on phases and holds bunch of mutable information in Tree and Symbol. And example of simple tree transformation between the phases. Nice. But zero details are revealed about API and inner structures, hooks and so on. What should I do if I need to alternate existing phase behaviour? Just go somewhere and see other plugin examples is what I told. I went and studied macroparadise sources. All I could say it is complicated and presumably uses heavily compiler inside machinery designed for mundane macroses.
So as I get from compiler presentation, I need to add two phases. Before the Typer I should hide all marked tree. And I should typecheck them afterward. So, how could I transform information between phases? Should I encode it in a custom expression type and unpack it later? How could I define such extension to the Tree structure? How could I get proper context to typecheck swallowed info later?