All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalactic.BooleanMacro.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2001-2012 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.scalactic

import scala.quoted._

object BooleanMacro {
  // Reference: https://www.scala-lang.org/api/3.0.0/scala/quoted/Quotes$reflectModule.html

  private val logicOperators = Set("&&", "||", "&", "|")

  private val supportedBinaryOperations =
    Set(
      "==",
      "!=",
      "===",
      "!==",
      "<",
      ">",
      ">=",
      "<=",
      "startsWith",
      "endsWith",
      "contains",
      "eq",
      "ne",
      "exists") ++ logicOperators

  def parse(condition: Expr[Boolean], prettifier: Expr[Prettifier])(using Quotes): Expr[Bool] = {
    import quotes.reflect._
    import quotes.reflect.ValDef.let
    import util._

    def exprStr: String = condition.show
    def defaultCase = '{ Bool.simpleMacroBool($condition, ${Expr(exprStr)}, $prettifier) }
    def isImplicitMethodType(tp: TypeRepr): Boolean = tp match {
      case tp: MethodType => tp.isImplicit
      case _ => false
    }

    def isByNameMethodType(tp: TypeRepr): Boolean =  tp.widen match {
      case MethodType(_, ByNameType(_) :: Nil, _) => true
      case _ => false
    }

    // use in `exists(_ == e)` or `exists(e == _)`
    //
    // Note: Scala2 implementation implicitly assumes `e` is side effect free,
    //       we do the same. A better approach would be to check `e` is Ident or
    //       Literal.
    //
    // {
    //   def $anonfun(_$12: Int): Boolean = _$12.==(2)
    //   closure($anonfun)
    // }
    object AnonFunction {
      def unapply(t: Term): Option[Term] = t match {
        case Block(
          ddef @
            DefDef(_, (ValDef(name, _, _) :: Nil) :: Nil, _,
              Some(Apply(Select(lhs, "=="), rhs :: Nil))
            ) :: Nil,
          clos
        ) if (clos.tpe.isFunctionType) => // walkaround: https://github.com/lampepfl/dotty/issues/6720
          (lhs, rhs) match {
            case (Ident(refName), _) if refName == name => Some(rhs)
            case (_, Ident(refName)) if refName == name => Some(lhs)
            case _ => None
          }
        case _ => None
      }
    }

    condition.asTerm.underlyingArgument match {
      case Apply(sel @ Select(Apply(qual, lhs :: Nil), op @ ("===" | "!==")), rhs :: Nil) =>
        let(Symbol.spliceOwner, lhs) { left =>
          let(Symbol.spliceOwner, rhs) { right =>
            let(Symbol.spliceOwner, qual.appliedTo(left).select(sel.symbol).appliedTo(right)) { result =>
              val l = left.asExpr
              val r = right.asExpr
              val b = result.asExprOf[Boolean]
              val code = '{ Bool.binaryMacroBool($l, ${ Expr(op) }, $r, $b, $prettifier) }
              code.asTerm
            }
          }
        }.asExprOf[Bool]

      case Apply(sel @ Select(lhs, op), rhs :: Nil) =>
        def binaryDefault =
          if (isByNameMethodType(sel.tpe)) defaultCase
          else if (supportedBinaryOperations.contains(op)) {
            let(Symbol.spliceOwner, lhs) { left =>
              let(Symbol.spliceOwner, rhs) { right =>
                val app = left.select(sel.symbol).appliedTo(right)
                let(Symbol.spliceOwner, app) { result =>
                  val l = left.asExpr
                  val r = right.asExpr
                  val b = result.asExprOf[Boolean]
                  val code = '{ Bool.binaryMacroBool($l, ${Expr(op)}, $r, $b, $prettifier) }
                  code.asTerm
                }
              }
            }.asExprOf[Bool]
          }
          else defaultCase

        op match {
          case "||" =>
            val left = parse(lhs.asExprOf[Boolean], prettifier)
            val right = parse(rhs.asExprOf[Boolean], prettifier)
            '{ $left || $right }
          case "|" =>
            val left = parse(lhs.asExprOf[Boolean], prettifier)
            val right = parse(rhs.asExprOf[Boolean], prettifier)
            '{ $left | $right }
          case "&&" =>
            val left = parse(lhs.asExprOf[Boolean], prettifier)
            val right = parse(rhs.asExprOf[Boolean], prettifier)
            '{ $left && $right }
          case "&" =>
            val left = parse(lhs.asExprOf[Boolean], prettifier)
            val right = parse(rhs.asExprOf[Boolean], prettifier)
            '{ $left & $right }
          case "==" =>
            lhs match {
              case Apply(sel @ Select(lhs0, op @ ("length" | "size")), Nil) =>
                let(Symbol.spliceOwner, lhs0) { left =>
                  let(Symbol.spliceOwner, rhs) { right =>
                    val actual = left.select(sel.symbol).appliedToArgs(Nil)
                    let(Symbol.spliceOwner, actual) { result =>
                      val l = left.asExpr
                      val r = right.asExpr
                      val res = result.asExpr
                      val code = '{ Bool.lengthSizeMacroBool($l, ${Expr(op)}, $res, $r, $prettifier) }
                      code.asTerm
                    }
                  }
                }.asExprOf[Bool]

              case sel @ Select(lhs0, op @ ("length" | "size")) =>
                let(Symbol.spliceOwner, lhs0) { left =>
                  let(Symbol.spliceOwner, rhs) { right =>
                    val actual = left.select(sel.symbol)
                    let(Symbol.spliceOwner, actual) { result =>
                      val l = left.asExpr
                      val r = right.asExpr
                      val res = result.asExpr
                      val code = '{ Bool.lengthSizeMacroBool($l, ${Expr(op)}, $res, $r, $prettifier) }
                      code.asTerm
                    }
                  }
                }.asExprOf[Bool]

              case _ =>
                binaryDefault
            }

          case ">" | "<" | ">=" | "<=" =>
            lhs match {
              case Apply(
                     Apply(
                       TypeApply(
                         Ident(infixOrderingOps), 
                         List(_)
                       ), 
                       List(wrapped)
                     ), 
                     List(_)
                   ) if infixOrderingOps == "infixOrderingOps" =>
                let(Symbol.spliceOwner, lhs) { left =>
                  let(Symbol.spliceOwner, rhs) { right =>
                    val app = left.select(sel.symbol).appliedTo(right)
                    let(Symbol.spliceOwner, app) { result =>
                      val l = wrapped.asExpr
                      val r = right.asExpr
                      val b = result.asExprOf[Boolean]
                      val code = '{ Bool.binaryMacroBool($l, ${Expr(op)}, $r, $b, $prettifier) }
                      code.asTerm
                    }
                  }
                }.asExprOf[Bool]
                        
              case _ => binaryDefault
            }

          case "exists" =>
            rhs match {
              case AnonFunction(rhsInner) => // see the assumption for `rhsInner` in `AnonFunction`
                let(Symbol.spliceOwner, lhs) { left =>
                  val app = left.select(sel.symbol).appliedTo(rhs)
                  let(Symbol.spliceOwner, app) { result =>
                    val l = left.asExpr
                    val r = rhsInner.asExpr
                    val res = result.asExprOf[Boolean]
                    val code = '{ Bool.existsMacroBool($l, $r, $res, $prettifier) }
                    code.asTerm
                  }
                }.asExprOf[Bool]
              case _ => defaultCase
            }
          case _ =>
            binaryDefault
        }

      case Apply(f @ Apply(sel @ Select(Apply(qual, lhs :: Nil), op @ ("===" | "!==")), rhs :: Nil), implicits)
      if isImplicitMethodType(f.tpe) =>
        let(Symbol.spliceOwner, lhs) { left =>
          let(Symbol.spliceOwner, rhs) { right =>
            val app = qual.appliedTo(left).select(sel.symbol).appliedTo(right).appliedToArgs(implicits)
            let(Symbol.spliceOwner, app) { result =>
              val l = left.asExpr
              val r = right.asExpr
              val b = result.asExprOf[Boolean]
              val code = '{ Bool.binaryMacroBool($l, ${ Expr(op) }, $r, $b, $prettifier) }
              code.asTerm
            }
          }
        }.asExprOf[Bool]

      case Apply(TypeApply(sel @ Select(lhs, op), targs), rhs :: Nil) =>
        let(Symbol.spliceOwner, lhs) { left =>
          let(Symbol.spliceOwner, rhs) { right =>
            val app = left.select(sel.symbol).appliedToTypes(targs.map(_.tpe)).appliedTo(right)
            let(Symbol.spliceOwner, app) { result =>
              val l = left.asExpr
              val r = right.asExpr
              val b = result.asExprOf[Boolean]
              val code = '{ Bool.binaryMacroBool($l, ${Expr(op)}, $r, $b, $prettifier) }
              code.asTerm
            }
          }
        }.asExprOf[Bool]

      case Apply(sel @ Select(lhs, op @ ("isEmpty" | "nonEmpty")), Nil) =>
        let(Symbol.spliceOwner, lhs) { l =>
          val res = l.select(sel.symbol).appliedToArgs(Nil).asExprOf[Boolean]
          '{ Bool.unaryMacroBool(${l.asExpr}, ${ Expr(op) }, $res, $prettifier) }.asTerm
        }.asExprOf[Bool]

      case Select(left, "unary_!") =>
        val receiver = parse(left.asExprOf[Boolean], prettifier)
        '{ !($receiver) }

      case sel @ Select(left, op @ ("isEmpty" | "nonEmpty")) =>
        let(Symbol.spliceOwner, left) { l =>
          val res = l.select(sel.symbol).asExprOf[Boolean]
          '{ Bool.unaryMacroBool(${l.asExpr}, ${ Expr(op) }, $res, $prettifier) }.asTerm
        }.asExprOf[Bool]

      case TypeApply(sel @ Select(lhs, "isInstanceOf"), targs) =>
        let(Symbol.spliceOwner, lhs) { l =>
          val res = l.select(sel.symbol).appliedToTypeTrees(targs).asExprOf[Boolean]
          val name = Expr(targs.head.tpe.show)
          '{ Bool.isInstanceOfMacroBool(${l.asExpr}, "isInstanceOf", $name, $res, $prettifier) }.asTerm
        }.asExprOf[Bool]

      case Literal(_) =>
        '{ Bool.simpleMacroBool($condition, "", $prettifier) }

      case _ =>
        defaultCase
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy