0

Given the following types:

sealed trait PosIntCheckResult
case class LteZero(x: Int) extends PosIntCheckResult
case object NotConstant extends PosIntCheckResult

I'm trying to write a macro that checks whether the given Int is greater than 0.

import reflect.macros.Context

def getInt(c: Context)(value: c.Expr[Int]): Either[PosIntCheckResult, Int] = {

    import c.universe._

    value.tree match {
        case Literal(Constant(x)) => if (x > 0) Right(x) else Left(LteZero(x))
        case _                    => Left(NotConstant)
    }
}

But Any shows up for the x value:

Test.scala:29: type mismatch;
  found   : Any
  required: Int
    case Literal(Constant(x)) => 
       if (x > 0) Right(x) else Left(LteZero(x))

How can I get the compiler to expect an Int rather than Any?

Ben Reich
  • 16,222
  • 2
  • 38
  • 59
Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384
  • Except for literal, I can hardly see an easy way to check whether an `Int` value is positive (e.g. `def foo(x: Int) = ???` hard to check `x` before runtime). You may want to have a look at https://github.com/non/spire/blob/master/README.md – cchantep Apr 28 '15 at 21:38
  • 1
    Kind of off topic but you may be interested in my blog post [here](https://meta.plasm.us/posts/2013/10/03/natural-vampires/) for a macro-based solution to a similar problem. – Travis Brown Apr 29 '15 at 02:36
  • somewhat related question in the scalatest forum - https://groups.google.com/forum/#!topic/scalatest-users/guIlCXHbgh8 – Kevin Meredith Apr 29 '15 at 13:47

1 Answers1

1

You just have to match on patterns where x is an Int:

case Literal(Constant(x: Int)) => //When using x here, it is an Int

Read about pattern matching on type and the other types of pattern matching in the docs.

You should also note that your macro will need to return an Expr in order to work. You can use reify to construct the desired Exprs at each case. Read about reify and def macros here. I'm not sure why this needs to be a macro, but if you're just learning the techniques, something like this might work:

object Macros {
    def getInt(value: Int): Either[PosIntCheckResult, Int] = macro getIntImpl

    def getIntImpl(c: Context)(value: c.Expr[Int]): c.Expr[Either[PosIntCheckResult,Int]] = {

        import c.universe._

        value.tree match {
            case x@Literal(Constant(const: Int)) if const > 0 => reify(Right(c.Expr[Int](x).splice))
            case x@Literal(Constant(_: Int)) => reify(Left(LteZero(c.Expr[Int](x).splice)))
            case _  => reify(Left(NotConstant))
        }
    }
}
Ben Reich
  • 16,222
  • 2
  • 38
  • 59
  • But this approach uses `asInstanceOf`? Is it a compile-time check? – Kevin Meredith Apr 28 '15 at 20:46
  • Pattern matching like this doesn't use `asInstanceOf`. This is a pattern matching syntax. It is checked at compile time, and will only match that case if `x` is an `Int`. – Ben Reich Apr 28 '15 at 20:53
  • But how about this [gist](https://gist.github.com/kevinmeredith/2575581c71e51c23da17)? I have not read JVM bytecode very much, but I see `instanceOf` in the instructions. Lastly, this post shows `isInstanceOf` [here](http://stackoverflow.com/a/3785053/409976), no? – Kevin Meredith Apr 28 '15 at 21:30
  • 2
    @KevinMeredith Yeah, the type cases here will desugar to runtime checks with `isInstanceOf`, and in general you're right to avoid that, but if there's anywhere it's probably not a big deal it's code that runs at compile time. – Travis Brown Apr 29 '15 at 02:34