It uses a solution using macros . The general approach here is to enrich Boolean so that it has a macro method that looks at the prefix context to find the comparison that was used to create this Boolean .
For example, suppose we have:
implicit class RichBooleanComparison(val x: Boolean) extends AnyVal { def <(rightConstant: Int): Boolean = macro Compare.ltImpl }
And the macro definition with the method header:
def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean]
Now suppose the compiler parses the expression 1 < 2 < 3 . Apparently, we could use c.prefix to get the expression 1 < 2 when evaluating the body of a macro object. However, the concept of permanent folding prevents us from doing this here. Constant folding is the process by which the compiler calculates the given constants at compile time. Thus, by the time macros are evaluated, c.prefix already complex as true in this case. We lost the expression 1 < 2 , which led to true . You can learn more about the permanent crease and their interactions with Scala macros on this issue and a little about this question .
If we limit the scope of discussion only to expressions of the form C1 < x < C2 , where C1 and C2 are constants, and x is a variable, then this becomes feasible, since this type is not affected by constant bending. Here is the implementation:
object Compare { def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean] = { import c.universe._ c.prefix.tree match { case Apply(_, Apply(Select( lhs@Literal (Constant(_)), _), ( x@Select (_, TermName(_))) :: Nil) :: Nil) => val leftConstant = c.Expr[Int](lhs) val variable = c.Expr[Int](x) reify((leftConstant.splice < variable.splice) && (variable.splice < rightConstant.splice)) case _ => c.abort(c.enclosingPosition, s"Invalid format. Must have format c1<x<c2, where c1 and c2 are constants, and x is variable.") } } }
Here we map the prefix context to the expected type, extract the corresponding parts ( lhs and x ), create new subtrees using c.Expr[Int] and create a new complete expression tree using reify and splice to make the desired three-way comparison. If the match with the expected type does not match, this will not compile.
This allows us to do:
val x = 5 1 < x < 5 //true 6 < x < 7 //false 3 < x < 4 //false
Optional!
macro documents , trees , and this presentation are good resources to learn more about macros.