Trait/Object

org.scalactic.anyvals

CompileTimeAssertions

Related Docs: object CompileTimeAssertions | package anyvals

Permalink

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 vai a macro wthat 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. Although you could write a factory method that throws a runtime exception if a non-odd Int is passed, we recommend a from method that returns an Option. The returned Option can be processed to deal with the potential for non-odd values.

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 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.from(value.splice).get }
  }
}

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 returns an Option, and since we've proven at compile time that that Option will be defined, we call get on it.

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.

Source
CompileTimeAssertions.scala
Linear Supertypes
AnyRef, Any
Known Subclasses
Ordering
  1. Alphabetic
  2. By inheritance
Inherited
  1. CompileTimeAssertions
  2. AnyRef
  3. Any
  1. Hide All
  2. Show all
Visibility
  1. Public
  2. All

Value Members

  1. final def !=(arg0: Any): Boolean

    Permalink
    Definition Classes
    AnyRef → Any
  2. final def ##(): Int

    Permalink
    Definition Classes
    AnyRef → Any
  3. final def ==(arg0: Any): Boolean

    Permalink
    Definition Classes
    AnyRef → Any
  4. final def asInstanceOf[T0]: T0

    Permalink
    Definition Classes
    Any
  5. def clone(): AnyRef

    Permalink
    Attributes
    protected[java.lang]
    Definition Classes
    AnyRef
    Annotations
    @throws( ... )
  6. def ensureValidCharLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Char], notValidMsg: String, notLiteralMsg: String)(isValid: (Char) ⇒ Boolean): Unit

    Permalink

    Ensures a given expression of type Char is a literal with a valid value according to a given validation function.

    Ensures a given expression of type Char is a literal with a valid value according to a given validation function.

    If the given Char expression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the given Char expression is not a literal, this method will complete abruptly with an exception whose detail message includes the String passed as notLiteralMsg. Otherwise, the given Char expression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes the String passed as notValidMsg.

    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 Char expression to validate

    notValidMsg

    a String message to include in the exception thrown if the expression is a literal, but not valid

    notLiteralMsg

    a String message 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

  7. def ensureValidDoubleLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Double], notValidMsg: String, notLiteralMsg: String)(isValid: (Double) ⇒ Boolean): Unit

    Permalink

    Ensures a given expression of type Double is a literal with a valid value according to a given validation function.

    Ensures a given expression of type Double is a literal with a valid value according to a given validation function.

    If the given Double expression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the given Double expression is not a literal, this method will complete abruptly with an exception whose detail message includes the String passed as notLiteralMsg. Otherwise, the given Double expression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes the String passed as notValidMsg.

    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 Double expression to validate

    notValidMsg

    a String message to include in the exception thrown if the expression is a literal, but not valid

    notLiteralMsg

    a String message 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

  8. def ensureValidFloatLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Float], notValidMsg: String, notLiteralMsg: String)(isValid: (Float) ⇒ Boolean): Unit

    Permalink

    Ensures a given expression of type Float is a literal with a valid value according to a given validation function.

    Ensures a given expression of type Float is a literal with a valid value according to a given validation function.

    If the given Float expression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the given Float expression is not a literal, this method will complete abruptly with an exception whose detail message includes the String passed as notLiteralMsg. Otherwise, the given Float expression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes the String passed as notValidMsg.

    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 Float expression to validate

    notValidMsg

    a String message to include in the exception thrown if the expression is a literal, but not valid

    notLiteralMsg

    a String message 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

  9. def ensureValidIntLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Int], notValidMsg: String, notLiteralMsg: String)(isValid: (Int) ⇒ Boolean): Unit

    Permalink

    Ensures a given expression of type Int is a literal with a valid value according to a given validation function.

    Ensures a given expression of type Int is a literal with a valid value according to a given validation function.

    If the given Int expression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the given Int expression is not a literal, this method will complete abruptly with an exception whose detail message includes the String passed as notLiteralMsg. Otherwise, the given Int expression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes the String passed as notValidMsg.

    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 Int expression to validate

    notValidMsg

    a String message to include in the exception thrown if the expression is a literal, but not valid

    notLiteralMsg

    a String message 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

  10. def ensureValidLongLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[Long], notValidMsg: String, notLiteralMsg: String)(isValid: (Long) ⇒ Boolean): Unit

    Permalink

    Ensures a given expression of type Long is a literal with a valid value according to a given validation function.

    Ensures a given expression of type Long is a literal with a valid value according to a given validation function.

    If the given Long expression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the given Long expression is not a literal, this method will complete abruptly with an exception whose detail message includes the String passed as notLiteralMsg. Otherwise, the given Long expression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes the String passed as notValidMsg.

    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 Long expression to validate

    notValidMsg

    a String message to include in the exception thrown if the expression is a literal, but not valid

    notLiteralMsg

    a String message 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

  11. def ensureValidStringLiteral(c: Context)(value: scala.reflect.macros.whitebox.Context.Expr[String], notValidMsg: String, notLiteralMsg: String)(isValid: (String) ⇒ Boolean): Unit

    Permalink

    Ensures a given expression of type String is a literal with a valid value according to a given validation function.

    Ensures a given expression of type String is a literal with a valid value according to a given validation function.

    If the given String expression is a literal whose value satisfies the given validation function, this method will return normally. Otherwise, if the given String expression is not a literal, this method will complete abruptly with an exception whose detail message includes the String passed as notLiteralMsg. Otherwise, the given String expression is a literal that does not satisfy the given validation function, so this method will complete abruptly with an exception whose detail message includes the String passed as notValidMsg.

    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 String expression to validate

    notValidMsg

    a String message to include in the exception thrown if the expression is a literal, but not valid

    notLiteralMsg

    a String message 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

  12. final def eq(arg0: AnyRef): Boolean

    Permalink
    Definition Classes
    AnyRef
  13. def equals(arg0: Any): Boolean

    Permalink
    Definition Classes
    AnyRef → Any
  14. def finalize(): Unit

    Permalink
    Attributes
    protected[java.lang]
    Definition Classes
    AnyRef
    Annotations
    @throws( classOf[java.lang.Throwable] )
  15. final def getClass(): Class[_]

    Permalink
    Definition Classes
    AnyRef → Any
  16. def hashCode(): Int

    Permalink
    Definition Classes
    AnyRef → Any
  17. final def isInstanceOf[T0]: Boolean

    Permalink
    Definition Classes
    Any
  18. final def ne(arg0: AnyRef): Boolean

    Permalink
    Definition Classes
    AnyRef
  19. final def notify(): Unit

    Permalink
    Definition Classes
    AnyRef
  20. final def notifyAll(): Unit

    Permalink
    Definition Classes
    AnyRef
  21. final def synchronized[T0](arg0: ⇒ T0): T0

    Permalink
    Definition Classes
    AnyRef
  22. def toString(): String

    Permalink
    Definition Classes
    AnyRef → Any
  23. final def wait(): Unit

    Permalink
    Definition Classes
    AnyRef
    Annotations
    @throws( ... )
  24. final def wait(arg0: Long, arg1: Int): Unit

    Permalink
    Definition Classes
    AnyRef
    Annotations
    @throws( ... )
  25. final def wait(arg0: Long): Unit

    Permalink
    Definition Classes
    AnyRef
    Annotations
    @throws( ... )

Inherited from AnyRef

Inherited from Any

Ungrouped