org.scalatest.matchers.MatchPatternMacro.scala Maven / Gradle / Ivy
/*
* Copyright 2001-2014 Artima, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.scalatest.matchers
import reflect.macros.Context
import org.scalatest.Resources
private[scalatest] object MatchPatternMacro {
/**
* Check the case definition AST, raise an compiler error if the body is not empty.
*/
def checkCaseDefinitions(context: Context)(tree: context.Tree) {
import context.universe._
// Check if it is a default case
def defaultCase(t: Tree): Boolean =
t match {
case Bind(defaultCaseTermName, Ident(nme.WILDCARD)) if defaultCaseTermName.decoded == "defaultCase$" => true // default case
case _ => false // not default case
}
tree match {
case Typed(Block(List(ClassDef(_, _, _, Template(_, _, List(_, DefDef(_, applyOrElseTermName, _, _, _, Match(_, caseDefList)), _)))), _), _) if applyOrElseTermName.decoded == "applyOrElse" =>
// We got a case definition list, let's go through them to check
caseDefList.foreach {
case CaseDef(pat, _, body) if !defaultCase(pat) => // case definition, and not default case
body match {
case Literal(Constant(())) => // ok, empty body
case _ => context.abort(body.pos, Resources.nonEmptyMatchPatternCase) // not empty body, raise compiler error
}
case _ => // other thing, just do nothing
}
case _ => // other thing, just do nothing
}
}
// Do checking on case definition and generate AST that returns a match pattern matcher
def matchPatternMatcher(context: Context)(right: context.Expr[PartialFunction[Any, _]]): context.Expr[Matcher[Any]] = {
import context.universe._
val tree = right.tree
// check case definitions
checkCaseDefinitions(context)(tree)
/**
* Generate AST for the following code:
*
* org.scalatest.matchers.MatchPatternHelper.matchPatternHelper(partialFunction)
*/
val callHelper =
Apply(
Select(
Select(
Select(
Select(
Ident(newTermName("org")),
newTermName("scalatest")
),
newTermName("matchers")
),
newTermName("MatchPatternHelper")
),
newTermName("matchPatternMatcher")
),
List(tree)
)
context.Expr(callHelper)
}
// Do checking on case definition and generate AST that returns a negated match pattern matcher
def notMatchPatternMatcherTree(context: Context)(right: context.Expr[PartialFunction[Any, _]]): context.Tree = {
import context.universe._
val tree = right.tree
// check case definitions
checkCaseDefinitions(context)(tree)
/**
* Generate AST for the following code:
*
* org.scalatest.matchers.MatchPatternHelper.notMatchPatternMatcher(partialFunction)
*/
Apply(
Select(
Select(
Select(
Select(
Ident(newTermName("org")),
newTermName("scalatest")
),
newTermName("matchers")
),
newTermName("MatchPatternHelper")
),
newTermName("notMatchPatternMatcher")
),
List(tree)
)
}
// Generate AST that returns a negated match pattern matcher expression
def notMatchPatternMatcher(context: Context)(right: context.Expr[PartialFunction[Any, _]]): context.Expr[Matcher[Any]] =
context.Expr(notMatchPatternMatcherTree(context)(right))
// Do checking on case definition and generate AST that does a 'and not' logical expression matcher.
def andNotMatchPatternMatcher(context: Context)(right: context.Expr[PartialFunction[Any, _]]): context.Expr[Matcher[Any]] = {
import context.universe._
val tree = right.tree
// Generate a negated matcher by calling notMatchPatternMatcher
val notMatcher = notMatchPatternMatcherTree(context)(right)
/**
* Generate AST for code that call the 'and' method on the Matcher instance (reference through 'owner'):
*
* owner.and(notMatcher)
*/
val callHelper =
context.macroApplication match {
case Apply(Select(qualifier, _), _) =>
Apply(
Select(
Select(
qualifier,
"owner"
),
newTermName("and")
),
List(notMatcher)
)
case _ => context.abort(context.macroApplication.pos, "This macro should be used with 'and not' syntax only.")
}
context.Expr(callHelper)
}
def orNotMatchPatternMatcher(context: Context)(right: context.Expr[PartialFunction[Any, _]]): context.Expr[Matcher[Any]] = {
import context.universe._
val tree = right.tree
// Generate a negated matcher by calling notMatchPatternMatcher
val notMatcher = notMatchPatternMatcherTree(context)(right)
/**
* Generate AST for code that call the 'and' method on the Matcher instance (reference through 'owner'):
*
* owner.or(notMatcher)
*/
val callHelper =
context.macroApplication match {
case Apply(Select(qualifier, _), _) =>
Apply(
Select(
Select(
qualifier,
"owner"
),
newTermName("or")
),
List(notMatcher)
)
case _ => context.abort(context.macroApplication.pos, "This macro should be used with 'or not' syntax only.")
}
context.Expr(callHelper)
}
/**
* Check case definitions and generate AST for code that check that the left match the pattern given on the right, which code looks like this:
*
* org.scalatest.matchers.MatchPatternHelper.checkPatternMatcher(left, right)
*/
def matchPattern(context: Context)(right: context.Expr[PartialFunction[Any, _]]): context.Expr[_] = {
import context.universe._
val tree = right.tree
// check case definitions
checkCaseDefinitions(context)(tree)
/**
* Generate AST for the following code:
*
* org.scalatest.matchers.MatchPatternHelper.checkPatternMatcher(left, right)
*/
val callHelper =
context.macroApplication match {
case Apply(Select(qualifier, _), _) =>
Apply(
Select(
Select(
Select(
Select(
Ident(newTermName("org")),
newTermName("scalatest")
),
newTermName("matchers")
),
newTermName("MatchPatternHelper")
),
newTermName("checkMatchPattern")
),
List(qualifier, tree)
)
case _ => context.abort(context.macroApplication.pos, "This macro should be used with should not syntax only.")
}
context.Expr(callHelper)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy