trait CompileTimeAssertions extends AnyRef
Trait providing assertion methods that can be called at compile time from macros to validate literals in source code.
The intent of CompileTimeAssertions is to make it easier to create
AnyVals that restrict the values of types for which Scala supports
literals: Int, Long, Float, Double, Char,
and String. For example, if you are using odd integers in many places
in your code, you might have validity checks scattered throughout your code. Here's
an example of a method that both requires an odd Int is passed (as a
precondition, and ensures an odd * Int is returned (as
a postcondition):
def nextOdd(i: Int): Int = { def isOdd(x: Int): Boolean = x.abs % 2 == 1 require(isOdd(i)) (i + 2) ensuring (isOdd(_)) }
In either the precondition or postcondition check fails, an exception will
be thrown at runtime. If you have many methods like this you may want to
create a type to represent an odd Int, so that the checking
for validity errors is isolated in just one place. By using an AnyVal
you can avoid boxing the Int, which may be more efficient.
This might look like:
final class OddInt private (val value: Int) extends AnyVal { override def toString: String = s"OddInt($value)" }
object OddInt { def apply(value: Int): OddInt = { require(value.abs % 2 == 1) new OddInt(value) } }
An AnyVal cannot have any constructor code, so to ensure that
any Int passed to the OddInt constructor is actually
odd, the constructor must be private. That way the only way to construct a
new OddInt is via the apply factory method in the
OddInt companion object, which can require that the value be
odd. This design eliminates the need for placing require and
ensuring clauses anywhere else that odd Ints are
needed, because the type promises the constraint. The nextOdd
method could, therefore, be rewritten as:
def nextOdd(oi: OddInt): OddInt = OddInt(oi.value + 2)
Using the compile-time assertions provided by this trait, you can construct
a factory method implemented via a macro that causes a compile failure
if OddInt.apply is passed anything besides an odd
Int literal. Class OddInt would look exactly the
same as before:
final class OddInt private (val value: Int) extends AnyVal { override def toString: String = s"OddInt($value)" }
In the companion object, however, the apply method would
be implemented in terms of a macro. Because the apply method
will only work with literals, you'll need a second method that can work
an any expression of type Int. We recommend a from method
that returns an Option[OddInt] that returns Some[OddInt} if the passed Int is odd,
else returns None, and an ensuringValid method that returns an OddInt
if the passed Int is valid, else throws AssertionError.
object OddInt {
// The from factory method validates at run time def from(value: Int): Option[OddInt] = if (OddIntMacro.isValid(value)) Some(new OddInt(value)) else None
// The ensuringValid factory method validates at run time, but throws // an AssertionError if invalid def ensuringValid(value: Int): OddInt = if (OddIntMacro.isValid(value)) new OddInt(value) else { throw new AssertionError(s"$value was not a valid OddInt") }
// The apply factory method validates at compile time import scala.language.experimental.macros def apply(value: Int): OddInt = macro OddIntMacro.apply }
The apply method refers to a macro implementation method in class
PosIntMacro. The macro implementation of any such method can look
very similar to this one. The only changes you'd need to make is the
isValid method implementation and the text of the error messages.
import org.scalactic.anyvals.CompileTimeAssertions import reflect.macros.Context
object OddIntMacro extends CompileTimeAssertions {
// Validation method used at both compile- and run-time def isValid(i: Int): Boolean = i.abs % 2 == 1
// Apply macro that performs a compile-time assertion def apply(c: Context)(value: c.Expr[Int]): c.Expr[OddInt] = {
// Prepare potential compiler error messages val notValidMsg = "OddInt.apply can only be invoked on odd Int literals, like OddInt(3)." val notLiteralMsg = "OddInt.apply can only be invoked on Int literals, like " + "OddInt(3). Please use OddInt.from instead."
// Validate via a compile-time assertion ensureValidIntLiteral(c)(value, notValidMsg, notLiteralMsg)(isValid)
// Validated, so rewrite the apply call to a from call c.universe.reify { OddInt.ensuringValid(value.splice) } } }
The isValid method just takes the underlying type and returns true if it is valid,
else false. This method is placed here so the same valiation code can be used both in
the from method at runtime and the apply macro at compile time. The apply
actually does just two things. It calls a ensureValidIntLiteral, performing a compile-time assertion
that value passed to apply is an Int literal that is valid (in this case, odd).
If the assertion fails, ensureValidIntLiteral will complete abruptly with an exception that will
contain an appropriate error message (one of the two you passed in) and cause a compiler error with that message.
If the assertion succeeds, ensureValidIntLiteral will just return normally. The next line of code
will then execute. This line of code must construct an AST (abstract syntax tree) of code that will replace
the OddInt.apply invocation. We invoke the other factory method that either returns an OddInt
or throws an AssertionError, since we've proven at compile time that the call will succeed.
You may wish to use quasi-quotes instead of reify. The reason we use reify is that this also works on 2.10 without any additional plugin (i.e., you don't need macro paradise), and Scalactic supports 2.10.
- Alphabetic
- By Inheritance
- CompileTimeAssertions
- AnyRef
- Any
- Hide All
- Show All
- Public
- Protected
Value Members
- final def !=(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def ##(): Int
- Definition Classes
- AnyRef → Any
- final def ==(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def asInstanceOf[T0]: T0
- Definition Classes
- Any
- def clone(): AnyRef
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.CloneNotSupportedException]) @native()
- def ensureValidCharLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Char], notValidMsg: String, notLiteralMsg: String)(isValid: (Char) => Boolean): Unit
Ensures a given expression of type
Charis a literal with a valid value according to a given validation function.Ensures a given expression of type
Charis a literal with a valid value according to a given validation function.If the given
Charexpression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the givenCharexpression is not a literal, this method will complete abruptly with an exception whose detail message includes theStringpassed asnotLiteralMsg. Otherwise, the givenCharexpression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes theStringpassed asnotValidMsg.This method is intended to be invoked at compile time from macros. When called from a macro, exceptions thrown by this method will result in compiler errors. The detail message of the thrown exception will appear as the compiler error message.
- c
the compiler context for this assertion
- value
the
Charexpression to validate- notValidMsg
a
Stringmessage to include in the exception thrown if the expression is a literal, but not valid- notLiteralMsg
a
Stringmessage to include in the exception thrown if the expression is not a literal- isValid
a function used to validate a literal value parsed from the given expression
- def ensureValidDoubleLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Double], notValidMsg: String, notLiteralMsg: String)(isValid: (Double) => Boolean): Unit
Ensures a given expression of type
Doubleis a literal with a valid value according to a given validation function.Ensures a given expression of type
Doubleis a literal with a valid value according to a given validation function.If the given
Doubleexpression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the givenDoubleexpression is not a literal, this method will complete abruptly with an exception whose detail message includes theStringpassed asnotLiteralMsg. Otherwise, the givenDoubleexpression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes theStringpassed asnotValidMsg.This method is intended to be invoked at compile time from macros. When called from a macro, exceptions thrown by this method will result in compiler errors. The detail message of the thrown exception will appear as the compiler error message.
- c
the compiler context for this assertion
- value
the
Doubleexpression to validate- notValidMsg
a
Stringmessage to include in the exception thrown if the expression is a literal, but not valid- notLiteralMsg
a
Stringmessage to include in the exception thrown if the expression is not a literal- isValid
a function used to validate a literal value parsed from the given expression
- def ensureValidFloatLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Float], notValidMsg: String, notLiteralMsg: String)(isValid: (Float) => Boolean): Unit
Ensures a given expression of type
Floatis a literal with a valid value according to a given validation function.Ensures a given expression of type
Floatis a literal with a valid value according to a given validation function.If the given
Floatexpression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the givenFloatexpression is not a literal, this method will complete abruptly with an exception whose detail message includes theStringpassed asnotLiteralMsg. Otherwise, the givenFloatexpression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes theStringpassed asnotValidMsg.This method is intended to be invoked at compile time from macros. When called from a macro, exceptions thrown by this method will result in compiler errors. The detail message of the thrown exception will appear as the compiler error message.
- c
the compiler context for this assertion
- value
the
Floatexpression to validate- notValidMsg
a
Stringmessage to include in the exception thrown if the expression is a literal, but not valid- notLiteralMsg
a
Stringmessage to include in the exception thrown if the expression is not a literal- isValid
a function used to validate a literal value parsed from the given expression
- def ensureValidIntLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Int], notValidMsg: String, notLiteralMsg: String)(isValid: (Int) => Boolean): Unit
Ensures a given expression of type
Intis a literal with a valid value according to a given validation function.Ensures a given expression of type
Intis a literal with a valid value according to a given validation function.If the given
Intexpression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the givenIntexpression is not a literal, this method will complete abruptly with an exception whose detail message includes theStringpassed asnotLiteralMsg. Otherwise, the givenIntexpression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes theStringpassed asnotValidMsg.This method is intended to be invoked at compile time from macros. When called from a macro, exceptions thrown by this method will result in compiler errors. The detail message of the thrown exception will appear as the compiler error message.
- c
the compiler context for this assertion
- value
the
Intexpression to validate- notValidMsg
a
Stringmessage to include in the exception thrown if the expression is a literal, but not valid- notLiteralMsg
a
Stringmessage to include in the exception thrown if the expression is not a literal- isValid
a function used to validate a literal value parsed from the given expression
- def ensureValidLongLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Long], notValidMsg: String, notLiteralMsg: String)(isValid: (Long) => Boolean): Unit
Ensures a given expression of type
Longis a literal with a valid value according to a given validation function.Ensures a given expression of type
Longis a literal with a valid value according to a given validation function.If the given
Longexpression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the givenLongexpression is not a literal, this method will complete abruptly with an exception whose detail message includes theStringpassed asnotLiteralMsg. Otherwise, the givenLongexpression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes theStringpassed asnotValidMsg.This method is intended to be invoked at compile time from macros. When called from a macro, exceptions thrown by this method will result in compiler errors. The detail message of the thrown exception will appear as the compiler error message.
- c
the compiler context for this assertion
- value
the
Longexpression to validate- notValidMsg
a
Stringmessage to include in the exception thrown if the expression is a literal, but not valid- notLiteralMsg
a
Stringmessage to include in the exception thrown if the expression is not a literal- isValid
a function used to validate a literal value parsed from the given expression
- def ensureValidStringLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[String], notValidMsg: String, notLiteralMsg: String)(isValid: (String) => Boolean): Unit
Ensures a given expression of type
Stringis a literal with a valid value according to a given validation function.Ensures a given expression of type
Stringis a literal with a valid value according to a given validation function.If the given
Stringexpression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the givenStringexpression is not a literal, this method will complete abruptly with an exception whose detail message includes theStringpassed asnotLiteralMsg. Otherwise, the givenStringexpression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes theStringpassed asnotValidMsg.This method is intended to be invoked at compile time from macros. When called from a macro, exceptions thrown by this method will result in compiler errors. The detail message of the thrown exception will appear as the compiler error message.
- c
the compiler context for this assertion
- value
the
Stringexpression to validate- notValidMsg
a
Stringmessage to include in the exception thrown if the expression is a literal, but not valid- notLiteralMsg
a
Stringmessage to include in the exception thrown if the expression is not a literal- isValid
a function used to validate a literal value parsed from the given expression
- final def eq(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- def equals(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef → Any
- def finalize(): Unit
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.Throwable])
- final def getClass(): Class[_ <: AnyRef]
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- def hashCode(): Int
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- final def isInstanceOf[T0]: Boolean
- Definition Classes
- Any
- final def ne(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- final def notify(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def notifyAll(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def synchronized[T0](arg0: => T0): T0
- Definition Classes
- AnyRef
- def toString(): String
- Definition Classes
- AnyRef → Any
- final def wait(): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long, arg1: Int): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException]) @native()