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

org.scalamock.clazz.MockMaker.scala Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2011-2015 ScalaMock Contributors (https://github.com/paulbutcher/ScalaMock/graphs/contributors)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package org.scalamock.clazz

import org.scalamock.context.MockContext
import org.scalamock.function._
import org.scalamock.util.MacroAdapter.Context
import org.scalamock.util.{MacroAdapter, MacroUtils}


//! TODO - get rid of this nasty two-stage construction when https://issues.scala-lang.org/browse/SI-5521 is fixed
class MockMaker[C <: Context](val ctx: C) {
  class MockMakerInner[T: ctx.WeakTypeTag](mockContext: ctx.Expr[MockContext], stub: Boolean, mockName: Option[ctx.Expr[String]]) {

    import ctx.universe._
    import Flag._
    import definitions._

    import scala.language.reflectiveCalls

    val utils = new MacroUtils[ctx.type](ctx)

    import utils._

    def mockFunctionClass(paramCount: Int): Type = paramCount match {
      case 0 => typeOf[MockFunction0[_]]
      case 1 => typeOf[MockFunction1[_, _]]
      case 2 => typeOf[MockFunction2[_, _, _]]
      case 3 => typeOf[MockFunction3[_, _, _, _]]
      case 4 => typeOf[MockFunction4[_, _, _, _, _]]
      case 5 => typeOf[MockFunction5[_, _, _, _, _, _]]
      case 6 => typeOf[MockFunction6[_, _, _, _, _, _, _]]
      case 7 => typeOf[MockFunction7[_, _, _, _, _, _, _, _]]
      case 8 => typeOf[MockFunction8[_, _, _, _, _, _, _, _, _]]
      case 9 => typeOf[MockFunction9[_, _, _, _, _, _, _, _, _, _]]
      case 10 => typeOf[MockFunction10[_, _, _, _, _, _, _, _, _, _, _]]
      case 11 => typeOf[MockFunction11[_, _, _, _, _, _, _, _, _, _, _, _]]
      case 12 => typeOf[MockFunction12[_, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 13 => typeOf[MockFunction13[_, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 14 => typeOf[MockFunction14[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 15 => typeOf[MockFunction15[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 16 => typeOf[MockFunction16[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 17 => typeOf[MockFunction17[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 18 => typeOf[MockFunction18[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 19 => typeOf[MockFunction19[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 20 => typeOf[MockFunction20[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 21 => typeOf[MockFunction21[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 22 => typeOf[MockFunction22[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case _ => ctx.abort(ctx.enclosingPosition, "ScalaMock: Can't handle methods with more than 22 parameters (yet)")
    }

    def stubFunctionClass(paramCount: Int): Type = paramCount match {
      case 0 => typeOf[StubFunction0[_]]
      case 1 => typeOf[StubFunction1[_, _]]
      case 2 => typeOf[StubFunction2[_, _, _]]
      case 3 => typeOf[StubFunction3[_, _, _, _]]
      case 4 => typeOf[StubFunction4[_, _, _, _, _]]
      case 5 => typeOf[StubFunction5[_, _, _, _, _, _]]
      case 6 => typeOf[StubFunction6[_, _, _, _, _, _, _]]
      case 7 => typeOf[StubFunction7[_, _, _, _, _, _, _, _]]
      case 8 => typeOf[StubFunction8[_, _, _, _, _, _, _, _, _]]
      case 9 => typeOf[StubFunction9[_, _, _, _, _, _, _, _, _, _]]
      case 10 => typeOf[StubFunction10[_, _, _, _, _, _, _, _, _, _, _]]
      case 11 => typeOf[StubFunction11[_, _, _, _, _, _, _, _, _, _, _, _]]
      case 12 => typeOf[StubFunction12[_, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 13 => typeOf[StubFunction13[_, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 14 => typeOf[StubFunction14[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 15 => typeOf[StubFunction15[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 16 => typeOf[StubFunction16[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 17 => typeOf[StubFunction17[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 18 => typeOf[StubFunction18[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 19 => typeOf[StubFunction19[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 20 => typeOf[StubFunction20[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 21 => typeOf[StubFunction21[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case 22 => typeOf[StubFunction22[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]]
      case _ => ctx.abort(ctx.enclosingPosition, "ScalaMock: Can't handle methods with more than 22 parameters (yet)")
    }

    def classType(paramCount: Int) = if (stub) stubFunctionClass(paramCount) else mockFunctionClass(paramCount)

    def isPathDependentThis(t: Type): Boolean = t match {
      case TypeRef(pre, _, _) => isPathDependentThis(pre)
      case ThisType(tpe) => tpe == typeToMock.typeSymbol
      case _ => false
    }

    /**
      * Translates forwarder parameters into Trees.
      * Also maps Java repeated params into Scala repeated params
      */
    def forwarderParamType(t: Type): Tree = t match {
      case TypeRef(pre, sym, args) if sym == JavaRepeatedParamClass =>
        TypeTree(internalTypeRef(pre, RepeatedParamClass, args))
      case TypeRef(pre, sym, args) if isPathDependentThis(t) =>
        AppliedTypeTree(Ident(TypeName(sym.name.toString)), args map TypeTree _)
      case _ =>
        TypeTree(t)
    }

    /**
     *  Translates mock function parameters into Trees.
     *  The difference between forwarderParamType is that:
     *  T* and T... are translated into Seq[T]
     *
     *  see issue #24
     */
    def mockParamType(t: Type): Tree = t match {
      case TypeRef(pre, sym, args) if sym == JavaRepeatedParamClass || sym == RepeatedParamClass =>
        AppliedTypeTree(Ident(typeOf[Seq[_]].typeSymbol), args map TypeTree _)
      case TypeRef(pre, sym, args) if isPathDependentThis(t) =>
        AppliedTypeTree(Ident(TypeName(sym.name.toString)), args map TypeTree _)
      case _ =>
        TypeTree(t)
    }

    def methodsNotInObject =
      typeToMock.members filter (m => m.isMethod && !isMemberOfObject(m)) map (_.asMethod)

    //! TODO - This is a hack, but it's unclear what it should be instead. See
    //! https://groups.google.com/d/topic/scala-user/n11V6_zI5go/discussion
    def resolvedType(m: Symbol): Type =
      m.typeSignatureIn(internalSuperType(internalThisType(typeToMock.typeSymbol), typeToMock))

    def buildForwarderParams(methodType: Type) =
      paramss(methodType) map { params =>
        params map { p =>
          ValDef(
            Modifiers(PARAM | (if (p.isImplicit) IMPLICIT else NoFlags)),
            TermName(p.name.toString),
            forwarderParamType(p.typeSignature),
            EmptyTree)
        }
      }

    // def <|name|>(p1: T1, p2: T2, ...): T = <|mockname|>(p1, p2, ...)
    def methodDef(m: MethodSymbol, methodType: Type, body: Tree): DefDef = {
      val params = buildForwarderParams(methodType)
      DefDef(
        Modifiers(OVERRIDE),
        m.name,
        m.typeParams map { p => internalTypeDef(p) },
        params,
        forwarderParamType(finalResultType(methodType)),
        body)
    }

    def methodImpl(m: MethodSymbol, methodType: Type, body: Tree): DefDef = {
      methodType match {
        case NullaryMethodType(_) => methodDef(m, methodType, body)
        case MethodType(_, _) => methodDef(m, methodType, body)
        case PolyType(_, _) => methodDef(m, methodType, body)
        case _ => ctx.abort(ctx.enclosingPosition,
          s"ScalaMock: Don't know how to handle ${methodType.getClass}. Please open a ticket at https://github.com/paulbutcher/ScalaMock/issues")
      }
    }

    def forwarderImpl(m: MethodSymbol): ValOrDefDef = {
      val mt = resolvedType(m)
      if (m.isStable) {
        ValDef(
          Modifiers(),
          TermName(m.name.toString),
          TypeTree(mt),
          castTo(literal(null), mt))
      } else {
        val body = applyListOn(
          Select(This(anon), mockFunctionName(m)), "apply",
          paramss(mt).flatten map { p => Ident(TermName(p.name.toString)) })
        methodImpl(m, mt, body)
      }
    }

    def mockFunctionName(m: MethodSymbol) = {
      val method = typeToMock.member(m.name).asTerm
      val index = method.alternatives.indexOf(m)
      assert(index >= 0)
      TermName("mock$" + m.name + "$" + index)
    }

    // val <|mockname|> = new MockFunctionN[T1, T2, ..., R](mockContext, '<|name|>)
    def mockMethod(m: MethodSymbol): ValDef = {
      val mt = resolvedType(m)
      val clazz = classType(paramCount(mt))
      val types = (paramTypes(mt) map mockParamType _) :+ mockParamType(finalResultType(mt))
      val name = applyOn(scalaSymbol, "apply", mockNameGenerator.generateMockMethodName(m, mt))
      val termName = mockFunctionName(m)
      val additionalAnnotations = if(isScalaJs) List(jsExport(termName.encodedName.toString)) else Nil
      ValDef(
        Modifiers().mapAnnotations(additionalAnnotations ::: _),
        mockFunctionName(m),
        AppliedTypeTree(Ident(clazz.typeSymbol), types), // see issue #24
        callConstructor(
          New(AppliedTypeTree(Ident(clazz.typeSymbol), types)),
          mockContext.tree, name
        )
      )
    }

    // def () = super.()
    def initDef = {
      val primaryConstructorOpt = typeToMock.members.collectFirst {
        case method: MethodSymbolApi if method.isPrimaryConstructor => method
      }

      val constructorArgumentsTypes = primaryConstructorOpt.map { constructor =>
      val constructorTypeContext = constructor.typeSignatureIn(typeToMock)
      val constructorArguments = constructor.paramLists
      constructorArguments.map {
          symbols => symbols.map(_.typeSignatureIn(constructorTypeContext))
        }
      }

      val tnEmpty = TypeName("") // typeNames.EMPTY
      val tnConstructor = TermName("") // termNames.CONSTRUCTOR
      val superCall: Tree = Select(Super(This(tnEmpty), tnEmpty), tnConstructor)
      val constructorCall = constructorArgumentsTypes.fold(Apply(superCall, Nil).asInstanceOf[Tree]) { symbols =>
        symbols.foldLeft(superCall) {
          case (acc, symbol) => Apply(acc, symbol.map(tpe => q"null.asInstanceOf[$tpe]"))
        }
      }

      DefDef(
        Modifiers(),
        tnConstructor,
        List(),
        List(List()),
        TypeTree(),
        Block(
          List(constructorCall),
          Literal(Constant(()))))
    }

    // new <|typeToMock|> { <|members|> }
    def anonClass(members: List[Tree]) =
      Block(
        List(
          ClassDef(
            Modifiers(FINAL),
            anon,
            List(),
            Template(
              List(TypeTree(typeToMock)),
              noSelfType,
              initDef +: members))),
        callConstructor(New(Ident(anon))))

    /**
     * Class that is responsible for creating mock (and its methods) names so they can be reported on expectations error.
     * It either uses mock name specified by user or asks mockContext to generate new one.
     */
    class MockNameGenerator() {
      private val mockNameValName = TermName("mock$special$mockName")

      /** Member of mock that holds mock name */
      val mockNameVal = {
        val mockNameValRhs = {
          if (mockName.nonEmpty) {
            // new String(mockNameExpr)
            callConstructor(New(scalaString), mockName.get.tree)
          } else {
            // mockContext.generateMockDefaultName(prefix).name
            val namePrefix = Literal(Constant(if (stub) "stub" else "mock"))
            selectTerm(applyOn(mockContext.tree, "generateMockDefaultName", namePrefix), "name")
          }
        }
        // val mock$special$mockName = ...
        ValDef(Modifiers(), mockNameValName, TypeTree(), mockNameValRhs)
      }

      def generateMockMethodName(method: MethodSymbol, methodType: Type): Tree = {
        val mockType: Type = typeToMock.resultType
        val mockTypeNamePart: String = mockType.typeSymbol.name.toString
        val mockTypeArgsPart: String = generateTypeArgsString(mockType.typeArgs)
        val objectNamePart: Tree = Select(This(anon), mockNameValName)

        val methodTypeParamsPart: String = generateTypeArgsString(methodType.typeParams map (_.name))
        val methodNamePart: String = method.name.toString

        // "<%s> %s%s.%s".format(objectNamePart, mockTypeNamePart, mockTypeArgsPart, methodNamePart, methodTypeParamsPart)
        val formatStr = applyOn(scalaPredef, "augmentString", literal("<%s> %s%s.%s%s"))
        applyOn(formatStr, "format",
          objectNamePart, literal(mockTypeNamePart), literal(mockTypeArgsPart), literal(methodNamePart), literal(methodTypeParamsPart))
      }

      private def generateTypeArgsString(typeArgs: List[Any]): String = {
        if (typeArgs.nonEmpty)
          "[%s]".format(typeArgs.mkString(","))
        else ""
      }
    }

    val mockNameGenerator = new MockNameGenerator()
    val typeToMock = weakTypeOf[T]
    val anon = TypeName("$anon")
    val methodsToMock = methodsNotInObject.filter { m =>
      !m.isConstructor && !m.isPrivate && m.privateWithin == NoSymbol &&
      !m.isFinal &&
        !m.asInstanceOf[reflect.internal.HasFlags].hasFlag(reflect.internal.Flags.BRIDGE) &&
        !m.isParamWithDefault && // see issue #43
        (!(m.isStable || m.isAccessor) ||
          m.asInstanceOf[reflect.internal.HasFlags].isDeferred) //! TODO - stop using internal if/when this gets into the API
    }.toList
    val forwarders = methodsToMock map forwarderImpl
    val mocks = methodsToMock map mockMethod
    val members = mockNameGenerator.mockNameVal :: forwarders ++ mocks

    def make = {
      val result = castTo(anonClass(members), typeToMock)

      //        println("------------")
      //        println(showRaw(result))
      //        println("------------")
      //        println(show(result))
      //        println("------------")

      ctx.Expr(result)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy