Haskell offers typed holes.
Example:
f :: Int -> String -> Bool
f x y = if (x > 10) then True else (g y)
g :: String -> Bool
g s = _
Compilation:
Prelude> :l HoleEx.hs
[1 of 1] Compiling Main ( HoleEx.hs, interpreted )
HoleEx.hs:6:7:
Found hole `_' with type: Bool
Relevant bindings include
s :: String (bound at HoleEx.hs:6:3)
g :: String -> Bool (bound at HoleEx.hs:6:1)
In the expression: _
In an equation for `g': g s = _
Failed, modules loaded: none.
When programming in Scala, I typically use ??? as placeholders in my Scala code that I've not yet written.
However, using typed holes appears to be more powerful to me. Note that I could replace the above else (g y) with else (g 55), yet I'd get a compile-time error:
Prelude> :l HoleEx.hs
[1 of 1] Compiling Main ( HoleEx.hs, interpreted )
HoleEx.hs:3:39:
Couldn't match type `Int' with `[Char]'
Expected type: String
Actual type: Int
In the first argument of `g', namely `x'
In the expression: (g x)
Failed, modules loaded: none.
Although I can use ??? in Scala to get placeholder implementations to compile, unlike holes, I'll get run-time errors with ???.
scala> def g(x: Int): Int = ???
g: (x: Int)Int
scala> g(5)
scala.NotImplementedError: an implementation is missing
Does Scala have typed holes?
For me a hole in a program helps me progress the implementation. It does that by telling me what type I need next. You can get something a little like that by triggering a regular Scala type error.
The trick is to define a type you know will be wrong, and then use it. For example:
object hole
You can then use that in code and you'll get an appropriate type error:
The error here is telling me the "hole" needs to be a B, suggesting I can progress by using g in some way. And if I progress the code to a => f(g(hole)) the compiler will tell me the hole needs to be an A.
That's a kind of hole, but unlike other languages (Idris etc)...
my program is just not compiling, rather than having a recognized hole as such;
there's no help to tell me the types in scope (although IDE code completion my give some suggestions);
I can't nicely name a hole;
I can't automatically lift a hole to a function definition;
I can't automatically solve the hole (as Idris can, sometimes);
I must type the expected return value, otherwise Scala will infer that hole is an acceptable type!
...and probably many other features.
As you say ??? is a useful placeholder, but it has type Nothing -- which means the program compiles, but it doesn't help you fill in the implementation.
At least object hole, although kind of trivial, does give you some type information to progress.
Scala's ??? is just a shorthand for throwing an exception, and is equivalent to Haskell's undefined or just error "not implemented" or similar.
The Scala compiler does not have support for typed holes. You can however just use ??? and inspect the type of that in an IDE or Emacs+Ensime to see its inferred type.
Scala's type inference is relatively minimal when compared to the full Hindley-Milner type inference of languages like Haskell. Which means that having something like typed holes in the Scala compiler would not be feasible.
There's a Scala compiler plugin by Chris Birchall called scala-typed-holes that implements typed holes:
package example
object Example {
def foo(x: Int, y: String): Boolean = {
if (y.length == x) {
??? // TODO implement!
} else {
true
}
}
def bar(x: Int): String = x match {
case 0 => "zero"
case 1 => "one"
case _ => ???
}
}
Generates:
[warn] /Users/chris/code/scala-typed-holes/src/test/scala/example/Example.scala:7:7:
[warn] Found hole with type: Boolean
[warn] Relevant bindings include
[warn] x: Int (bound at Example.scala:5:11
[warn] y: String (bound at Example.scala:5:19)
[warn]
[warn] ??? // TODO implement!
[warn] ^
[warn] /Users/chris/code/scala-typed-holes/src/test/scala/example/Example.scala:16:15:
[warn] Found hole with type: String
[warn] Relevant bindings include
[warn] x: Int (bound at Example.scala:13:11)
[warn]
[warn] case _ => ???
[warn] ^
Related
I am trying to build a map from an input map, but the compiler is unable to prove that a 2-element tuple is a 2-element tuple.
Code
class Element[T] extends AnyRef { }
class Sample
{
def makeList(x:Int): Element[_] = {
x match {
case 1 => new Element[Boolean]
case 2 => new Element[(Boolean, Boolean)]
}
}
val input = Map(1 -> "one",2 -> "two")
val output = input.map(e => e._1 -> makeList(e._1)).toMap
}
sbt compile
sbt:root> ~compile
[info] Compiling 1 Scala source to /Users/tda0106/test/scala/target/scala-2.12/classes ...
[error] /Users/tda0106/test/scala/src/main/scala/Test.scala:14:57: Cannot prove that (Int, Element[_$1]) forSome { type _$1 } <:< (T, U).
[error] val output = input.map(e => e._1 -> makeList(e._1)).toMap
[error] ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 1 s, completed Jun 27, 2019, 2:38:14 PM
It appears that the problem is related to the forSome { type _$1 }, as otherwise it should match. When I first tried to reproduce it, I used List instead of Element and it compiled. It appears that the different is that List is declared as List[+T] and the + is important here.
Element is from a third party library, so changing it is difficult.
What is the problem that I am running into here, and is there a simple way to fix it?
Scala version: 2.12.8
Scala gets fickle about type inference sometimes when you're doing things with existentials (which is what Element[_] is). A quick explicit type signature will fix that right up.
val output = input.map(e => e._1 -> makeList(e._1)).toMap[Int, Element[_]]
All you're doing is telling the compiler what types you want for the keys and values. The reasons why it can't infer this are long and complicated, but as a general rule once you start throwing underscores in your types, you're going to lose some inference capabilities.
I have a function that expects a variable number of parameters of the same type, which sounds like the textbook use case for varargs:
def myFunc[A](as: A*) = ???
The problem I have is that myFunc cannot accept empty parameter lists. There's a trivial way of enforcing that at runtime:
def myFunc[A](as: A*) = {
require(as.nonEmpty)
???
}
The problem with that is that it happens at runtime, as opposed to compile time. I would like the compiler to reject myFunc().
One possible solution would be:
def myFunc[A](head: A, tail: A*) = ???
And this works when myFunc is called with inline arguments, but I'd like users of my library to be able to pass in a List[A], which this syntax makes very awkward.
I could try to have both:
def myFunc[A](head: A, tail: A*) = myFunc(head +: tail)
def myFunc[A](as: A*) = ???
But we're right back where we started: there's now a way of calling myFunc with an empty parameter list.
I'm aware of scalaz's NonEmptyList, but in as much as possible, I'd like to stay with stlib types.
Is there a way to achieve what I have in mind with just the standard library, or do I need to accept some runtime error handling for something that really feels like the compiler should be able to deal with?
What about something like this?
scala> :paste
// Entering paste mode (ctrl-D to finish)
def myFunc()(implicit ev: Nothing) = ???
def myFunc[A](as: A*) = println(as)
// Exiting paste mode, now interpreting.
myFunc: ()(implicit ev: Nothing)Nothing <and> [A](as: A*)Unit
myFunc: ()(implicit ev: Nothing)Nothing <and> [A](as: A*)Unit
scala> myFunc(3)
WrappedArray(3)
scala> myFunc(List(3): _*)
List(3)
scala> myFunc()
<console>:13: error: could not find implicit value for parameter ev: Nothing
myFunc()
^
scala>
Replacing Nothing with a class that has an appropriate implicitNotFound annotation should allow for a sensible error message.
Let's start out with what I think is your base requirement: the ability to define myFunc in some way such that the following occurs at the Scala console when a user provides literals. Then maybe if we can achieve that, we can try to go for varargs.
myFunc(List(1)) // no problem
myFunc(List[Int]()) // compile error!
Moreover, we don't want to have to force users either to split a list into a head and tail or have them convert to a ::.
Well when we're given literals, since we have access to the syntax used to construct the value, we can use macros to verify that a list is non-empty. Moreover, there's already a library that'll do it for us, namely refined!
scala> refineMV[NonEmpty]("Hello")
res2: String Refined NonEmpty = Hello
scala> refineMV[NonEmpty]("")
<console>:39: error: Predicate isEmpty() did not fail.
refineMV[NonEmpty]("")
^
Unfortunately this is still problematic in your case, because you'll need to put refineMV into the body of your function at which point the literal syntactically disappears and macro magic fails.
Okay what about the general case that doesn't rely on syntax?
// Can we do this?
val xs = getListOfIntsFromStdin() // Pretend this function exists
myFunc(xs) // compile error if xs is empty
Well now we're up against a wall; there's no way a compile time error can happen here since the code has already been compiled and yet clearly xs could be empty. We'll have to deal with this case at runtime, either in a type-safe manner with Option and the like or with something like runtime exceptions. But maybe we can do a little better than just throw our hands up in the air. There's two possible paths of improvement.
Somehow provide implicit evidence that xs is nonempty. If the compiler can find that evidence, then great! If not, it's on the user to provide it somehow at runtime.
Track the provenance of xs through your program and statically prove that it must be non-empty. If this cannot be proved, either error out at compile time or somehow force the user to handle the empty case.
Once again, unfortunately this is problematic.
I strongly suspect this is not possible (but this is still only a suspicion and I would be happy to be proved wrong). The reason is that ultimately implicit resolution is type-directed which means that Scala gets the ability to do type-level computation on types, but Scala has no mechanism that I know of to do type-level computation on values (i.e. dependent typing). We require the latter here because List(1, 2, 3) and List[Int]() are indistinguishable at the type level.
Now you're in SMT solver land, which does have some efforts in other languages (hello Liquid Haskell!). Sadly I don't know of any such efforts in Scala (and I imagine it would be a harder task to do in Scala).
The bottom line is that when it comes to error checking there is no free lunch. A compiler can't magically make error handling go away (although it can tell you when you don't strictly need it), the best it can do is yell at you when you forget to handle certain classes of errors, which is itself very valuable. To underscore the no free lunch point, let's return to a language that does have dependent types (Idris) and see how it handles non-empty values of List and the prototypical function that breaks on empty lists, List.head.
First we get a compile error on empty lists
Idris> List.head []
(input):1:11:When checking argument ok to function Prelude.List.head:
Can't find a value of type
NonEmpty []
Good, what about non-empty lists, even if they're obfuscated by a couple of leaps?
Idris> :let x = 5
-- Below is equivalent to
-- val y = identity(Some(x).getOrElse(3))
Idris> :let y = maybe 3 id (Just x)
-- Idris makes a distinction between Natural numbers and Integers
-- Disregarding the Integer to Nat conversion, this is
-- val z = Stream.continually(2).take(y)
Idris> :let z = Stream.take (fromIntegerNat y) (Stream.repeat 2)
Idris> List.head z
2 : Integer
It somehow works! What if we really don't let the Idris compiler know anything about the number we pass along and instead get one at runtime from the user? We blow up with a truly gargantuan error message that starts with When checking argument ok to function Prelude.List.head: Can't find a value of type NonEmpty...
import Data.String
generateN1s : Nat -> List Int
generateN1s x = Stream.take x (Stream.repeat 1)
parseOr0 : String -> Nat
parseOr0 str = case parseInteger str of
Nothing => 0
Just x => fromIntegerNat x
z : IO Int
z = do
x <- getLine
let someNum = parseOr0 x
let firstElem = List.head $ generateN1s someNum -- Compile error here
pure firstElem
Hmmm... well what's the type signature of List.head?
Idris> :t List.head
-- {auto ...} is roughly the same as Scala's implicit
head : (l : List a) -> {auto ok : NonEmpty l} -> a
Ah so we just need to provide a NonEmpty.
data NonEmpty : (xs : List a) -> Type where
IsNonEmpty : NonEmpty (x :: xs)
Oh a ::. And we're back at square one.
Use scala.collection.immutable.::
:: is the cons of the list
defined in std lib
::[A](head: A, tail: List[A])
use :: to define myFunc
def myFunc[A](list: ::[A]): Int = 1
def myFunc[A](head: A, tail: A*): Int = myFunc(::(head, tail.toList))
Scala REPL
scala> def myFunc[A](list: ::[A]): Int = 1
myFunc: [A](list: scala.collection.immutable.::[A])Int
scala> def myFunc[A](head: A, tail: A*): Int = myFunc(::(head, tail.toList))
myFunc: [A](head: A, tail: A*)Int
Consider the following example:
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
In this example, I know (runtime guarantee) that the type of a will be some T, and b will have type C[T] with the same T. Of course, the compiler cannot know that, hence we get a typing error in b.f(a).
To tell the compiler that this invocation is OK, we need to do a typecast à la b.f(a.asInstanceOf[T]). Unfortunately, T is not known here. So my question is: How do I rewrite b.f(a) in order to make this code compile?
I am looking for a solution that does not involve complex constructions (to keep the code readable), and that is "clean" in the sense that we should not rely on code erasure to make it work (see the first approach below).
I have some working approaches, but I find them unsatisfactory for various reasons.
Approaches I tried:
b.asInstanceOf[C[Any]].f(a)
This works, and is reasonably readable, but it is based on a "lie". b is not of type C[Any], and the only reason we do not get a runtime error is because we rely on the limitations of the JVM (type erasure). I think it is good style only to use x.asInstanceOf[X] when we know that x is really of type X.
b.f(a.asInstanceOf[b.ValueType])
This should work according to my understanding of the type system. I have added the member ValueType to the class C in order to be able to explicitly refer to the type parameter T. However, in this approach we get a mysterious error message:
Error:(9, 22) type mismatch;
found : b.ValueType
(which expands to) _1
required: _1
b.f(a.asInstanceOf[b.ValueType])
^
Why? It seems to complain that we expect type _1 but got type _1! (But even if this approach works, it is limited to the cases where we have the possibility to add a member ValueType to C. If C is some existing library class, we cannot do that either.)
for ((a,b) <- list.asInstanceOf[List[(T,C[T]) forSome {type T}]]) {
b.f(a)
}
This one works, and is semantically correct (i.e., we do not "lie" when invoking asInstanceOf). The limitation is that this is somewhat unreadable. Also, it is somewhat specific to the present situation: if a,b do not come from the same iterator, then where can we apply this type cast? (This code also has the side effect of being too complex for Intelli/J IDEA 2016.2 which highlights it as an error in the editor.)
val (a2,b2) = (a,b).asInstanceOf[(T,C[T]) forSome {type T}]
b2.f(a2)
I would have expected this one to work since a2,b2 now should have types T and C[T] for the same existential T. But we get a compile error:
Error:(10, 9) type mismatch;
found : a2.type (with underlying type Any)
required: T
b2.f(a2)
^
Why? (Besides that, the approach has the disadvantage of incurring runtime costs (I think) because of the creation and destruction of a pair.)
b match {
case b : C[t] => b.f(a.asInstanceOf[t])
}
This works. But enclosing the code with a match makes the code much less readable. (And it also is too complicated for Intelli/J.)
The cleanest solution is, IMO, the one you found with the type-capture pattern match. You can make it concise, and hopefully readable, by integrating the pattern directly inside your for comprehension, as follows:
for ((a, b: C[t]) <- list) {
b.f(a.asInstanceOf[t])
}
Fiddle: http://www.scala-js-fiddle.com/gist/b9030033133ee94e8c18ad772f3461a0
If you are not in a for comprehension already, unfortunately the corresponding pattern assignment does not work:
val (c, d: C[t]) = (a, b)
d.f(c.asInstanceOf[t])
That's because t is not in scope anymore on the second line. In that case, you would have to use the full pattern matching.
Maybe I'm confused about what you are trying to achieve, but this compiles:
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
type CP[T] = (T, C[T])
val list = List[CP[T forSome {type T}]](1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
Edit
If the type of the list itself is out of your control, you can still cast it to this "correct" type.
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
type CP[T] = (T, C[T])
for ((a,b) <- list.asInstanceOf[List[CP[T forSome { type T }]]]) {
b.f(a)
}
Great question! Lots to learn here about Scala.
Other answers and comments have already addressed most of the issues here, but I'd like to address a few additional points.
You asked why this variant doesn't work:
val (a2,b2) = (a,b).asInstanceOf[(T,C[T]) forSome {type T}]
b2.f(a2)
You aren't the only person who's been surprised by this; see e.g. this recent very similar issue report: SI-9899.
As I wrote there:
I think this is working as designed as per SLS 6.1: "The following skolemization rule is applied universally for every expression: If the type of an expression would be an existential type T, then the type of the expression is assumed instead to be a skolemization of T."
Basically, every time you write a value-level expression that the compiler determines to have an existential type, the existential type is instantiated. b2.f(a2) has two subexpressions with existential type, namely b2 and a2, so the existential gets two different instantiations.
As for why the pattern-matching variant works, there isn't explicit language in SLS 8 (Pattern Matching) covering the behavior of existential types, but 6.1 doesn't apply because a pattern isn't technically an expression, it's a pattern. The pattern is analyzed as a whole and any existential types inside only get instantiated (skolemized) once.
As a postscript, note that yes, when you play in this area, the error messages you get are often confusing or misleading and ought to be improved. See for example https://github.com/scala/scala-dev/issues/205
A wild guess, but is it possible that you need something like this:
case class C[+T](x:T) {
def f[A >: T](t: A) = println(t)
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
?
It will type check.
I'm not quite sure what "runtime guarantee" means here, usually it means that you are trying to fool type system (e.g. with asInstanceOf), but then all bets are off and you shouldn't expect type system to be of any help.
UPDATE
Just for the illustration why type casting is an evil:
case class C[T <: Int](x:T) {
def f(t: T) = println(t + 1)
}
val list = List("hello" -> C(2), 2 -> C(3))
for ((a, b: C[t]) <- list) {
b.f(a.asInstanceOf[t])
}
It compiles and fails at runtime (not surprisingly).
UPDATE2
Here's what generated code looks like for the last snippet (with C[t]):
...
val a: Object = x1._1();
val b: Test$C = x1._2().$asInstanceOf[Test$C]();
if (b.ne(null))
{
<synthetic> val x2: Test$C = b;
matchEnd4({
x2.f(scala.Int.unbox(a));
scala.runtime.BoxedUnit.UNIT
})
}
...
Type t simply vanished (as it should have been) and Scala is trying to convert a to an upper bound of T in C, i.e. Int. If there is no upper bound it's going to be Any (but then method f is nearly useless unless you cast again or use something like println which takes Any).
What does this warning mean:
reflective access of structural type member method getMap should be enabled
The warning includes a reference to the scala docs but I don't understand how my code is related to the explanation. (Specifically the explanation mentions reflection... how is my code using reflection?)
I have this: (Scala 2.11.2)
object getMap {
implicit def fromOptionToConvertedVal[T](o:Option[T]) = new {
def getMap[R] (doWithSomeVal:(T) => R) = new {
def orElse(handleNone: => R) = o match {
case Some(value) => doWithSomeVal(value)
case None => handleNone
}
}
}
}
import getMap._
val i:Option[Int] = Some(5)
val x = i getMap (_*2) orElse 1
This generates the warning below:
[warn] /Users/Greg/git/Favorites-Demo/src/main/scala/com/rs/server/ThriftServer.scala:34: reflective access of structural type member method getMap should be enabled
[warn] by making the implicit value scala.language.reflectiveCalls visible.
[warn] This can be achieved by adding the import clause 'import scala.language.reflectiveCalls'
[warn] or by setting the compiler option -language:reflectiveCalls.
[warn] See the Scala docs for value scala.language.reflectiveCalls for a discussion
[warn] why the feature should be explicitly enabled.
[warn] val x = i getMap (_*2) orElse 1
[warn] ^
[warn] /Users/Greg/git/Favorites-Demo/src/main/scala/com/rs/server/ThriftServer.scala:34: reflective access of structural type member method orElse should be enabled
[warn] by making the implicit value scala.language.reflectiveCalls visible.
[warn] val x = i getMap (_*2) orElse 1
[warn] ^
I think what's going on is that the new { ... } objects are structurally typed, which require reflection to implement.
The underlying reason is that Scala structural typing allows objects to be treated as if they are instances of many types according to which methods they actually have (like duck typing). The JVM allows exactly one type so methods that are not part of the object's underlying type have to be accessed via something other than a normal virtual method call. The mechanism that's used in this case is reflection.
When the scala compiler sees an invocation of a method that's being called on a structurally typed object it (modulo some optimizations) translates a method call like:
a.f(b, c)
to
a.getClass
.getMethod("f", Array(classOf[B], classOf[C]))
.invoke(a, Array(b, c))
Example taken from the place where the technique was described.
The Scala team decided to enforce an opt-in policy for advanced features. reflectiveCalls happens to be one of them as documented in this SIP
The problem
When I'm working with libraries that support type-level programming, I often find myself writing comments like the following (from an example presented by Paul Snively at Strange Loop 2012):
// But these invalid sequences don't compile:
// isValid(_3 :: _1 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: _5 :: HNil)
// isValid(_3 :: _4 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: HNil)
Or this, from an example in the Shapeless repository:
/**
* If we wanted to confirm that the list uniquely contains `Foo` or any
* subtype of `Foo`, we could first use `unifySubtypes` to upcast any
* subtypes of `Foo` in the list to `Foo`.
*
* The following would not compile, for example:
*/
//stuff.unifySubtypes[Foo].unique[Foo]
This is a very rough way of indicating some fact about the behavior of these methods, and we could imagine wanting to make these assertions more formal—for unit or regression testing, etc.
To give a concrete example of why this might be useful in the context of a library like Shapeless, a few days ago I wrote the following as a quick first attempt at an answer to this question:
import shapeless._
implicit class Uniqueable[L <: HList](l: L) {
def unique[A](implicit ev: FilterAux[L, A, A :: HNil]) = ev(l).head
}
Where the intention is that this will compile:
('a' :: 'b :: HNil).unique[Char]
While this will not:
('a' :: 'b' :: HNil).unique[Char]
I was surprised to find that this implementation of a type-level unique for HList didn't work, because Shapeless would happily find a FilterAux instance in the latter case. In other words, the following would compile, even though you'd probably expect it not to:
implicitly[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
In this case, what I was seeing was a bug—or at least something bug-ish—and it has since been fixed.
More generally, we can imagine wanting to check the kind of invariant that was implicit in my expectations about how FilterAux should work with something like a unit test—as weird as it may sound to be talking about testing type-level code like this, with all the recent debates about the relative merit of types vs. tests.
My question
The problem is that I don't know of any kind of testing framework (for any platform) that allows the programmer to assert that something must not compile.
One approach that I can imagine for the FilterAux case would be to use the old implicit-argument-with-null-default trick:
def assertNoInstanceOf[T](implicit instance: T = null) = assert(instance == null)
Which would let you write the following in your unit test:
assertNoInstanceOf[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
The following would be a heck of a lot more convenient and expressive, though:
assertDoesntCompile(('a' :: 'b' :: HNil).unique[Char])
I want this. My question is whether anyone knows of any testing library or framework that supports anything remotely like it—ideally for Scala, but I'll settle for anything.
Not a framework, but Jorge Ortiz (#JorgeO) mentioned some utilities he added to the tests for Foursquare's Rogue library at NEScala in 2012 which support tests for non-compilation: you can find examples here. I've been meaning to add something like this to shapeless for quite a while.
More recently, Roland Kuhn (#rolandkuhn) has added a similar mechanism, this time using Scala 2.10's runtime compilation, to the tests for Akka typed channels.
These are both dynamic tests of course: they fail at (test) runtime if something that shouldn't compile does. Untyped macros might provide a static option: ie. a macro could accept an untyped tree, type check it and throw a type error if it succeeds). This might be something to experiment with on the macro-paradise branch of shapeless. But not a solution for 2.10.0 or earlier, obviously.
Update
Since answering the question, another approach, due to Stefan Zeiger (#StefanZeiger), has surfaced. This one is interesting because, like the untyped macro one alluded to above, it is a compile time rather than (test) runtime check, however it is also compatible with Scala 2.10.x. As such I think it is preferable to Roland's approach.
I've now added implementations to shapeless for 2.9.x using Jorge's approach, for 2.10.x using Stefan's approach and for macro paradise using the untyped macro approach. Examples of the corresponding tests can be found here for 2.9.x, here for 2.10.x and here for macro paradise.
The untyped macro tests are the cleanest, but Stefan's 2.10.x compatible approach is a close second.
ScalaTest 2.1.0 has the following syntax for Assertions:
assertTypeError("val s: String = 1")
And for Matchers:
"val s: String = 1" shouldNot compile
Do you know about partest in the Scala project? E.g. CompilerTest has the following doc:
/** For testing compiler internals directly.
* Each source code string in "sources" will be compiled, and
* the check function will be called with the source code and the
* resulting CompilationUnit. The check implementation should
* test for what it wants to test and fail (via assert or other
* exception) if it is not happy.
*/
It is able to check for example whether this source https://github.com/scala/scala/blob/master/test/files/neg/divergent-implicit.scala will have this result https://github.com/scala/scala/blob/master/test/files/neg/divergent-implicit.check
It's not a perfect fit for your question (since you don't specify your test cases in terms of asserts), but may be an approach and/or give you a head start.
Based on the links provided by Miles Sabin I was able to use the akka version
import scala.tools.reflect.ToolBox
object TestUtils {
def eval(code: String, compileOptions: String = "-cp target/classes"): Any = {
val tb = mkToolbox(compileOptions)
tb.eval(tb.parse(code))
}
def mkToolbox(compileOptions: String = ""): ToolBox[_ <: scala.reflect.api.Universe] = {
val m = scala.reflect.runtime.currentMirror
m.mkToolBox(options = compileOptions)
}
}
Then in my tests I used it like this
def result = TestUtils.eval(
"""|import ee.ui.events.Event
|import ee.ui.events.ReadOnlyEvent
|
|val myObj = new {
| private val writableEvent = Event[Int]
| val event:ReadOnlyEvent[Int] = writableEvent
|}
|
|// will not compile:
|myObj.event.fire
|""".stripMargin)
result must throwA[ToolBoxError].like {
case e =>
e.getMessage must contain("value fire is not a member of ee.ui.events.ReadOnlyEvent[Int]")
}
The compileError macro in µTest does just that:
compileError("true * false")
// CompileError.Type("value * is not a member of Boolean")
compileError("(}")
// CompileError.Parse("')' expected but '}' found.")