diff --git a/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMMacro.scala b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMMacro.scala new file mode 100644 index 000000000000..ffa4a95cf226 --- /dev/null +++ b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMMacro.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2020 John A. De Goes and the ZIO Contributors + * + * 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 zio.macros + +import scala.reflect.macros.whitebox + +/** + * Generates method accessors for a service parametrized by HKT into annotated object + */ +private[macros] class AccessibleMMMacro(override val c: whitebox.Context) extends AccessibleMMacroBase(c) { + + import c.universe._ + + private lazy val io: Tree = tq"_root_.zio.IO" + private lazy val rio: Tree = tq"_root_.zio.RIO" + private lazy val urio: Tree = tq"_root_.zio.URIO" + + private lazy val managed: Tree = tq"_root_.zio.Managed" + private lazy val rManaged: Tree = tq"_root_.zio.RManaged" + private lazy val urManaged: Tree = tq"_root_.zio.URManaged" + + protected lazy val macroName: String = "accessibleMM" + + protected lazy val aliases: Seq[Tree] = Seq(io, rio, urio, managed, rManaged, urManaged) + + protected def expectedTypeParams: Long = 2 +} diff --git a/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMacro.scala b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMacro.scala new file mode 100644 index 000000000000..1957e8666876 --- /dev/null +++ b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMacro.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2020 John A. De Goes and the ZIO Contributors + * + * 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 zio.macros + +import scala.reflect.macros.whitebox + +/** + * Generates method accessors for a service parametrized by HKT into annotated object + */ +private[macros] class AccessibleMMacro(override val c: whitebox.Context) extends AccessibleMMacroBase(c) { + + import c.universe._ + + private lazy val task: Tree = tq"_root_.zio.Task" + private lazy val uio: Tree = tq"_root_.zio.UIO" + + private lazy val taskManaged: Tree = tq"_root_.zio.TaskManaged" + private lazy val uManaged: Tree = tq"_root_.zio.UManaged" + + protected lazy val macroName: String = "accessibleM" + + protected lazy val aliases: Seq[Tree] = Seq(task, uio, taskManaged, uManaged) + + protected def expectedTypeParams: Long = 1 + +} diff --git a/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMacroBase.scala b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMacroBase.scala new file mode 100644 index 000000000000..cf7c484f02d0 --- /dev/null +++ b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMMacroBase.scala @@ -0,0 +1,88 @@ +/* + * Copyright 2019-2020 John A. De Goes and the ZIO Contributors + * + * 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 zio.macros + +import scala.reflect.macros.whitebox + +import com.github.ghik.silencer.silent + +private[macros] abstract class AccessibleMMacroBase(override val c: whitebox.Context) extends AccessibleMacroBase(c) { + + import c.universe._ + + protected def expectedTypeParams: Long + + protected def aliases: Seq[Tree] + + @silent("pattern var [^\\s]+ in method unapply is never used") + protected val tp: Tree = c.prefix.tree match { + case q"new ${macroNm: Ident}[$typeParam]" if macroNm.name == TypeName(macroName) => + typeParam + case _ => + abort("could not unquote annotation. Make sure you have specified the type parameter.") + } + + protected val tpTpe: Type = { + val res = c.typecheck(tq"$tp", c.TYPEmode).tpe + if (types.keySet.contains(res)) + res + else + abort(s"unsupported type constructor $res. Supported constructors: ${types.keySet}") + } + + private lazy val types: Map[Type, Tree] = + aliases.map(a => c.typecheck(tq"$a", c.TYPEmode).tpe -> a).toMap + + @silent("pattern var [^\\s]+ in method unapply is never used") + protected def macroApply(annottees: Seq[c.Tree]): MacroApply = new MacroApply(annottees) { + + private val typeParamToInject = { + val candidates = + moduleInfo.serviceTypeParams.filter(tp => tp.tparams.size == expectedTypeParams) + + candidates match { + case Seq(c) => c + case Seq() => abort(s"`Service` doesn't have type param for [$tpTpe]") + case nonEmpty => abort(s"`Service` contains several possible candidates for [$tpTpe]: $nonEmpty") + } + } + + protected def treeTpe(tree: Tree): Type = + tree match { + case tq"${typeName: Ident}[..${typeParams}]" => + val typeArgs = typeParams.map(p => c.typecheck(tq"$p", c.TYPEmode, silent = true).tpe) + val shouldInject = typeName.name == typeParamToInject.name + + val injectedTypeName = + if (shouldInject) types(tpTpe) + else typeName + + c.typecheck(tq"$injectedTypeName[..$typeArgs]", c.TYPEmode).tpe + case _ => abort(s"could not unquote return type tree $tree") + } + + override protected def typeArgsForService(serviceTypeParams: List[TypeDef]): List[TypeName] = + serviceTypeParams.map { + case `typeParamToInject` => TypeName(tp.toString) + case other => other.name + } + + override protected def typeParamsForAccessors(serviceTypeParams: List[TypeDef]): List[TypeDef] = + serviceTypeParams.filterNot(_ == typeParamToInject) + } + +} diff --git a/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMacro.scala b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMacro.scala index 643870061f16..bf4d8b124bc4 100644 --- a/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMacro.scala +++ b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMacro.scala @@ -23,189 +23,26 @@ import com.github.ghik.silencer.silent /** * Generates method accessors for a service into annotated object. */ -private[macros] class AccessibleMacro(val c: whitebox.Context) { - import c.universe._ +private[macros] class AccessibleMacro(override val c: whitebox.Context) extends AccessibleMacroBase(c) { - protected case class ModuleInfo( - module: ModuleDef, - service: ClassDef, - serviceTypeParams: List[TypeDef] - ) + import c.universe._ - def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) + protected val macroName: String = "accessible" @silent("pattern var [^\\s]+ in method unapply is never used") - def apply(annottees: c.Tree*): c.Tree = { - - val any: Tree = tq"_root_.scala.Any" - val throwable: Tree = tq"_root_.java.lang.Throwable" - - val moduleInfo = (annottees match { - case (module: ModuleDef) :: Nil => - module.impl.body.collectFirst { - case service @ ClassDef(_, name, tparams, _) if name.toTermName.toString == "Service" => - ModuleInfo(module, service, tparams) - } - case _ => None - }).getOrElse(abort("@accessible macro can only be applied to objects containing `Service` trait.")) - - sealed trait Capability - object Capability { - case class Effect(r: Tree, e: Tree, a: Tree) extends Capability - case class Managed(r: Tree, e: Tree, a: Tree) extends Capability - case class Method(a: Tree) extends Capability - case class Sink(r: Tree, e: Tree, a: Tree, l: Tree, b: Tree) extends Capability - case class Stream(r: Tree, e: Tree, a: Tree) extends Capability - } - - case class TypeInfo(capability: Capability) { - - val r: Tree = capability match { - case Capability.Effect(r, _, _) => r - case Capability.Managed(r, _, _) => r - case Capability.Sink(r, _, _, _, _) => r - case Capability.Stream(r, _, _) => r - case Capability.Method(_) => any - } - - val e: Tree = capability match { - case Capability.Effect(_, e, _) => e - case Capability.Managed(_, e, _) => e - case Capability.Sink(_, e, _, _, _) => e - case Capability.Stream(_, e, _) => e - case Capability.Method(_) => throwable - } - - val a: Tree = capability match { - case Capability.Effect(_, _, a) => a - case Capability.Managed(_, _, a) => a - case Capability.Sink(_, e, a, l, b) => tq"_root_.zio.stream.ZSink[$any, $e, $a, $l, $b]" - case Capability.Stream(_, e, a) => tq"_root_.zio.stream.ZStream[$any, $e, $a]" - case Capability.Method(a) => a - } - } - - def typeInfo(tree: Tree): TypeInfo = + override def macroApply(annottees: Seq[c.Tree]): MacroApply = new MacroApply(annottees) { + protected def treeTpe(tree: Tree): Type = tree match { case tq"$typeName[..$typeParams]" => - val typeArgs = typeParams.map(t => c.typecheck(tq"$t", c.TYPEmode, silent = true).tpe) - val tpe = c.typecheck(tq"$typeName[..$typeArgs]", c.TYPEmode).tpe - val dealiased = tpe.dealias - val replacements: List[Tree] = (tpe.typeArgs zip typeParams).collect { case (NoType, t) => - q"$t" - } - - val (typeArgTrees, _) = dealiased.typeArgs.foldLeft(List.empty[Tree] -> replacements) { - case ((acc, x :: xs), NoType) => (acc :+ x) -> xs - case ((acc, xs), t) => (acc :+ q"$t") -> xs - } - - (dealiased.typeSymbol.fullName, typeArgTrees) match { - case ("zio.ZIO", r :: e :: a :: Nil) => TypeInfo(Capability.Effect(r, e, a)) - case ("zio.ZManaged", r :: e :: a :: Nil) => TypeInfo(Capability.Managed(r, e, a)) - case ("zio.stream.ZSink", r :: e :: a :: l :: b :: Nil) => TypeInfo(Capability.Sink(r, e, a, l, b)) - case ("zio.stream.ZStream", r :: e :: a :: Nil) => TypeInfo(Capability.Stream(r, e, a)) - case _ => TypeInfo(Capability.Method(tree)) - } + val typeArgs = typeParams.map(t => c.typecheck(tq"$t", c.TYPEmode, silent = true).tpe) + c.typecheck(tq"$typeName[..$typeArgs]", c.TYPEmode).tpe } - def makeAccessor( - name: TermName, - info: TypeInfo, - serviceTypeParams: List[TypeDef], - typeParams: List[TypeDef], - paramLists: List[List[ValDef]], - isVal: Boolean - ): Tree = { + override protected def typeArgsForService(serviceTypeParams: List[TypeDef]): List[TypeName] = + serviceTypeParams.map(_.name) - val serviceTypeArgs = serviceTypeParams.map(_.name) - - val returnType = info.capability match { - case Capability.Effect(r, e, a) => - if (r != any) tq"_root_.zio.ZIO[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a]" - else tq"_root_.zio.ZIO[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a]" - case Capability.Managed(r, e, a) => - if (r != any) tq"_root_.zio.ZManaged[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a]" - else tq"_root_.zio.ZManaged[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a]" - case Capability.Stream(r, e, a) => - if (r != any) tq"_root_.zio.stream.ZStream[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a]" - else tq"_root_.zio.stream.ZStream[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a]" - case Capability.Sink(r, e, a, l, b) => - if (r != any) tq"_root_.zio.stream.ZSink[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a, $l, $b]" - else tq"_root_.zio.stream.ZSink[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a, $l, $b]" - case Capability.Method(a) => - tq"_root_.zio.ZIO[_root_.zio.Has[Service[..$serviceTypeArgs]], $throwable, $a]" - } - - val typeArgs = typeParams.map(_.name) - - def isRepeatedParamType(vd: ValDef) = vd.tpt match { - case AppliedTypeTree(Select(_, nme), _) if nme == definitions.RepeatedParamClass.name => true - case _ => false - } - - val argNames = paramLists.map(_.map { arg => - if (isRepeatedParamType(arg)) q"${arg.name}: _*" - else q"${arg.name}" - }) - - val returnValue = (info.capability, paramLists) match { - case (_: Capability.Effect, argLists) if argLists.flatten.nonEmpty => - q"_root_.zio.ZIO.accessM(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" - case (_: Capability.Effect, _) => - q"_root_.zio.ZIO.accessM(_.get[Service[..$serviceTypeArgs]].$name)" - case (_: Capability.Managed, argLists) if argLists.flatten.nonEmpty => - q"_root_.zio.ZManaged.accessManaged(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" - case (_: Capability.Managed, _) => - q"_root_.zio.ZManaged.accessManaged(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs])" - case (_: Capability.Stream, argLists) if argLists.flatten.nonEmpty => - q"_root_.zio.stream.ZStream.accessStream(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" - case (_: Capability.Stream, _) => - q"_root_.zio.stream.ZStream.accessStream(_.get[Service[..$serviceTypeArgs]].$name)" - case (_: Capability.Sink, argLists) if argLists.flatten.nonEmpty => - q"_root_.zio.stream.ZSink.accessSink(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" - case (_: Capability.Sink, _) => - q"_root_.zio.stream.ZSink.accessSink(_.get[Service[..$serviceTypeArgs]].$name)" - case (_, argLists) if argLists.flatten.nonEmpty => - val argNames = argLists.map(_.map(_.name)) - q"_root_.zio.ZIO.access(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" - case (_, _) => - q"_root_.zio.ZIO.access(_.get[Service[..$serviceTypeArgs]].$name)" - } - - if (isVal && serviceTypeParams.isEmpty) q"val $name: $returnType = $returnValue" - else { - val allTypeParams = - serviceTypeParams.map(tp => TypeDef(Modifiers(Flag.PARAM), tp.name, tp.tparams, tp.rhs)) ::: typeParams - paramLists match { - case Nil => - q"def $name[..$allTypeParams](implicit ev: _root_.izumi.reflect.Tag[Service[..$serviceTypeArgs]]): $returnType = $returnValue" - case List(Nil) => - q"def $name[..$allTypeParams]()(implicit ev: _root_.izumi.reflect.Tag[Service[..$serviceTypeArgs]]): $returnType = $returnValue" - case _ => - q"def $name[..$allTypeParams](...$paramLists)(implicit ev: _root_.izumi.reflect.Tag[Service[..$serviceTypeArgs]]): $returnType = $returnValue" - } - } - } - - val accessors = - moduleInfo.service.impl.body.collect { - case DefDef(_, termName, tparams, argLists, tree: Tree, _) if termName != TermName("$init$") => - makeAccessor(termName, typeInfo(tree), moduleInfo.serviceTypeParams, tparams, argLists, isVal = false) - - case ValDef(_, termName, tree: Tree, _) => - makeAccessor(termName, typeInfo(tree), moduleInfo.serviceTypeParams, Nil, Nil, isVal = true) - } - - moduleInfo.module match { - case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" => - q""" - $mods object $tname extends { ..$earlydefns } with ..$parents { $self => - ..$body - ..$accessors - } - """ - case _ => abort("@accessible macro failure - could not unquote annotated object.") - } + override protected def typeParamsForAccessors(serviceTypeParams: List[TypeDef]): List[TypeDef] = + serviceTypeParams } + } diff --git a/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMacroBase.scala b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMacroBase.scala new file mode 100644 index 000000000000..55ee52e8e887 --- /dev/null +++ b/macros/shared/src/main/scala-2.x/zio/macros/AccessibleMacroBase.scala @@ -0,0 +1,230 @@ +/* + * Copyright 2019-2020 John A. De Goes and the ZIO Contributors + * + * 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 zio.macros + +import scala.reflect.macros.whitebox + +import com.github.ghik.silencer.silent + +private[macros] abstract class AccessibleMacroBase(val c: whitebox.Context) { + + import c.universe._ + + protected val any: Tree = tq"_root_.scala.Any" + protected val throwable: Tree = tq"_root_.java.lang.Throwable" + + protected val zioServiceName: TermName = TermName("Service") + protected val constructorName: TermName = TermName("$init$") + + protected case class ModuleInfo( + module: ModuleDef, + service: ClassDef, + serviceTypeParams: List[TypeDef] + ) + + protected sealed trait Capability + + object Capability { + case class Effect(r: Tree, e: Tree, a: Tree) extends Capability + case class Managed(r: Tree, e: Tree, a: Tree) extends Capability + case class Method(a: Tree) extends Capability + case class Sink(r: Tree, e: Tree, a: Tree, l: Tree, b: Tree) extends Capability + case class Stream(r: Tree, e: Tree, a: Tree) extends Capability + } + + protected case class TypeInfo(capability: Capability) { + + val r: Tree = capability match { + case Capability.Effect(r, _, _) => r + case Capability.Managed(r, _, _) => r + case Capability.Sink(r, _, _, _, _) => r + case Capability.Stream(r, _, _) => r + case Capability.Method(_) => any + } + + val e: Tree = capability match { + case Capability.Effect(_, e, _) => e + case Capability.Managed(_, e, _) => e + case Capability.Sink(_, e, _, _, _) => e + case Capability.Stream(_, e, _) => e + case Capability.Method(_) => throwable + } + + val a: Tree = capability match { + case Capability.Effect(_, _, a) => a + case Capability.Managed(_, _, a) => a + case Capability.Sink(_, e, a, l, b) => tq"_root_.zio.stream.ZSink[$any, $e, $a, $l, $b]" + case Capability.Stream(_, e, a) => tq"_root_.zio.stream.ZStream[$any, $e, $a]" + case Capability.Method(a) => a + } + } + + protected val macroName: String + + protected def macroApply(annottees: Seq[c.Tree]): MacroApply + + final def apply(annottees: c.Tree*): c.Tree = macroApply(annottees)() + + protected def abort(msg: String): Nothing = c.abort(c.enclosingPosition, s"@$macroName macro failure - $msg") + + protected abstract class MacroApply(annottees: Seq[c.Tree]) { + + protected def treeTpe(tree: Tree): Type + + protected def typeArgsForService(serviceTypeParams: List[TypeDef]): List[TypeName] + + protected def typeParamsForAccessors(serviceTypeParams: List[TypeDef]): List[TypeDef] + + protected lazy val moduleInfo: ModuleInfo = (annottees match { + case (module: ModuleDef) :: Nil => + module.impl.body.collectFirst { + case service @ ClassDef(_, name, tparams, _) if name.toTermName == zioServiceName => + ModuleInfo(module, service, tparams) + } + case _ => None + }).getOrElse(abort(s"@$macroName macro can only be applied to objects containing `Service` trait.")) + + @silent("pattern var [^\\s]+ in method unapply is never used") + private def typeInfo(tree: Tree): TypeInfo = + tree match { + case tq"$_[..$typeParams]" => + val tpe = treeTpe(tree) + val dealiased = tpe.dealias + val replacements: List[Tree] = + (tpe.typeArgs zip typeParams).collect { case (NoType, t) => q"$t" } + + val (typeArgTrees, _) = dealiased.typeArgs.foldLeft(List.empty[Tree] -> replacements) { + case ((acc, x :: xs), NoType) => (acc :+ x) -> xs + case ((acc, xs), t) => (acc :+ q"$t") -> xs + } + + (dealiased.typeSymbol.fullName, typeArgTrees) match { + case ("zio.ZIO", r :: e :: a :: Nil) => TypeInfo(Capability.Effect(r, e, a)) + case ("zio.ZManaged", r :: e :: a :: Nil) => TypeInfo(Capability.Managed(r, e, a)) + case ("zio.stream.ZSink", r :: e :: a :: l :: b :: Nil) => TypeInfo(Capability.Sink(r, e, a, l, b)) + case ("zio.stream.ZStream", r :: e :: a :: Nil) => TypeInfo(Capability.Stream(r, e, a)) + case _ => TypeInfo(Capability.Method(tree)) + } + } + + private def makeAccessor( + name: TermName, + info: TypeInfo, + serviceTypeParams: List[TypeDef], + typeParams: List[TypeDef], + paramLists: List[List[ValDef]], + isVal: Boolean + ): Tree = { + + val serviceTypeArgs = typeArgsForService(serviceTypeParams) + + val returnType = info.capability match { + case Capability.Effect(r, e, a) => + if (r != any) tq"_root_.zio.ZIO[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a]" + else tq"_root_.zio.ZIO[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a]" + case Capability.Managed(r, e, a) => + if (r != any) tq"_root_.zio.ZManaged[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a]" + else tq"_root_.zio.ZManaged[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a]" + case Capability.Stream(r, e, a) => + if (r != any) tq"_root_.zio.stream.ZStream[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a]" + else tq"_root_.zio.stream.ZStream[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a]" + case Capability.Sink(r, e, a, l, b) => + if (r != any) tq"_root_.zio.stream.ZSink[_root_.zio.Has[Service[..$serviceTypeArgs]] with $r, $e, $a, $l, $b]" + else tq"_root_.zio.stream.ZSink[_root_.zio.Has[Service[..$serviceTypeArgs]], $e, $a, $l, $b]" + case Capability.Method(a) => + tq"_root_.zio.ZIO[_root_.zio.Has[Service[..$serviceTypeArgs]], $throwable, $a]" + } + + val typeArgs = typeParams.map(_.name) + + def isRepeatedParamType(vd: ValDef) = vd.tpt match { + case AppliedTypeTree(Select(_, nme), _) => nme == definitions.RepeatedParamClass.name + case _ => false + } + + val argNames = paramLists.map(_.map { arg => + if (isRepeatedParamType(arg)) q"${arg.name}: _*" + else q"${arg.name}" + }) + + val returnValue = (info.capability, paramLists) match { + case (_: Capability.Effect, argLists) if argLists.flatten.nonEmpty => + q"_root_.zio.ZIO.accessM(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" + case (_: Capability.Effect, _) => + q"_root_.zio.ZIO.accessM(_.get[Service[..$serviceTypeArgs]].$name)" + case (_: Capability.Managed, argLists) if argLists.flatten.nonEmpty => + q"_root_.zio.ZManaged.accessManaged(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" + case (_: Capability.Managed, _) => + q"_root_.zio.ZManaged.accessManaged(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs])" + case (_: Capability.Stream, argLists) if argLists.flatten.nonEmpty => + q"_root_.zio.stream.ZStream.accessStream(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" + case (_: Capability.Stream, _) => + q"_root_.zio.stream.ZStream.accessStream(_.get[Service[..$serviceTypeArgs]].$name)" + case (_: Capability.Sink, argLists) if argLists.flatten.nonEmpty => + q"_root_.zio.stream.ZSink.accessSink(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" + case (_: Capability.Sink, _) => + q"_root_.zio.stream.ZSink.accessSink(_.get[Service[..$serviceTypeArgs]].$name)" + case (_, argLists) if argLists.flatten.nonEmpty => + val argNames = argLists.map(_.map(_.name)) + q"_root_.zio.ZIO.access(_.get[Service[..$serviceTypeArgs]].$name[..$typeArgs](...$argNames))" + case (_, _) => + q"_root_.zio.ZIO.access(_.get[Service[..$serviceTypeArgs]].$name)" + } + + val accessorTypeParams = typeParamsForAccessors(serviceTypeParams) + + if (isVal && accessorTypeParams.isEmpty) q"val $name: $returnType = $returnValue" + else { + val allTypeParams = + accessorTypeParams.map(tp => TypeDef(Modifiers(Flag.PARAM), tp.name, tp.tparams, tp.rhs)) ::: typeParams + paramLists match { + case Nil => + q"def $name[..$allTypeParams](implicit ev: _root_.izumi.reflect.Tag[Service[..$serviceTypeArgs]]): $returnType = $returnValue" + case List(Nil) => + q"def $name[..$allTypeParams]()(implicit ev: _root_.izumi.reflect.Tag[Service[..$serviceTypeArgs]]): $returnType = $returnValue" + case _ => + q"def $name[..$allTypeParams](...$paramLists)(implicit ev: _root_.izumi.reflect.Tag[Service[..$serviceTypeArgs]]): $returnType = $returnValue" + } + } + } + + @silent("pattern var [^\\s]+ in method unapply is never used") + final def apply(): c.Tree = { + + val accessors = + moduleInfo.service.impl.body.collect { + case DefDef(_, termName, tparams, argLists, tree: Tree, _) if termName != constructorName => + makeAccessor(termName, typeInfo(tree), moduleInfo.serviceTypeParams, tparams, argLists, isVal = false) + + case ValDef(_, termName, tree: Tree, _) => + makeAccessor(termName, typeInfo(tree), moduleInfo.serviceTypeParams, Nil, Nil, isVal = true) + } + + moduleInfo.module match { + case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" => + q""" + $mods object $tname extends { ..$earlydefns } with ..$parents { $self => + ..$body + ..$accessors + } + """ + case _ => abort("could not unquote annotated object") + } + } + } + +} diff --git a/macros/shared/src/main/scala-2.x/zio/macros/accessible.scala b/macros/shared/src/main/scala-2.x/zio/macros/accessible.scala index ad6be9a49d55..c4d5f0d7bdaf 100644 --- a/macros/shared/src/main/scala-2.x/zio/macros/accessible.scala +++ b/macros/shared/src/main/scala-2.x/zio/macros/accessible.scala @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package zio.macros import scala.annotation.{ StaticAnnotation, compileTimeOnly } diff --git a/macros/shared/src/main/scala-2.x/zio/macros/accessibleM.scala b/macros/shared/src/main/scala-2.x/zio/macros/accessibleM.scala new file mode 100644 index 000000000000..c03b19d8d734 --- /dev/null +++ b/macros/shared/src/main/scala-2.x/zio/macros/accessibleM.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2019-2020 John A. De Goes and the ZIO Contributors + * + * 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 zio.macros + +import scala.annotation.{ StaticAnnotation, compileTimeOnly } + +@compileTimeOnly("enable macro paradise to expand macro annotations") +class accessibleM[F[_]] extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro AccessibleMMacro.apply +} diff --git a/macros/shared/src/main/scala-2.x/zio/macros/accessibleMM.scala b/macros/shared/src/main/scala-2.x/zio/macros/accessibleMM.scala new file mode 100644 index 000000000000..c015003fb345 --- /dev/null +++ b/macros/shared/src/main/scala-2.x/zio/macros/accessibleMM.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2019-2020 John A. De Goes and the ZIO Contributors + * + * 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 zio.macros + +import scala.annotation.{ StaticAnnotation, compileTimeOnly } + +@compileTimeOnly("enable macro paradise to expand macro annotations") +class accessibleMM[F[_, _]] extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro AccessibleMMMacro.apply +} diff --git a/macros/shared/src/test/scala-2.x/zio/macros/AccessibleMMSpec.scala b/macros/shared/src/test/scala-2.x/zio/macros/AccessibleMMSpec.scala new file mode 100644 index 000000000000..d244af243074 --- /dev/null +++ b/macros/shared/src/test/scala-2.x/zio/macros/AccessibleMMSpec.scala @@ -0,0 +1,457 @@ +package zio.macros + +import zio._ +import zio.test.Assertion._ +import zio.test._ + +object AccessibleMMSpec extends DefaultRunnableSpec { + + def spec: ZSpec[Environment, Failure] = suite("AccessibleMMSpec")( + suite("AccessibleMM macro")( + testM("compiles when applied to object with empty Service") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_, _]] + } + """ + })(isRight(anything)) + }, + testM("fails when applied to object without a Service") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module + """ + })(isLeft(anything)) + }, + testM("fails when applied to trait") { + assertM(typeCheck { + """ + @accessibleMM[IO] + trait Module[F[_, _]] + """ + })(isLeft(anything)) + }, + testM("fails when applied to class") { + assertM(typeCheck { + """ + @accessibleMM[IO] + class Module[F[_, _]] + """ + })(isLeft(anything)) + }, + testM("fails when applied to object with Service without type param") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service + } + """ + })(isLeft(anything)) + }, + testM("fails when applied to Service with Service without suitable type param") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_]] + } + """ + })(isLeft(anything)) + }, + testM("fails when applied to Service with Service with multiple suitable type params") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_, _], G[_, _]] + } + """ + })(isLeft(anything)) + }, + testM("fails when applied to non-ZIO type param") { + assertM(typeCheck { + """ + @accessibleMM[Either] + object Module { + trait Service[F[_, _]] + } + """ + })(isLeft(anything)) + }, + testM("generates accessor for values") { + assertM(typeCheck { + """ + @accessibleMM[URIO] + object Module { + trait Service[F[_, _]] { + val foo: F[Has[Unit], Unit] + } + } + + object Check { + val foo: ZIO[Has[Module.Service[URIO]] with Has[Unit], Nothing, Unit] = + Module.foo + } + """ + })(isRight(anything)) + }, + testM("generates accessor for functions") { + assertM(typeCheck { + """ + @accessibleMM[URIO] + object Module { + trait Service[F[_, _]] { + def foo(i: Int): F[Has[Unit], Unit] + } + } + + object Check { + def foo(i: Int): ZIO[Has[Module.Service[URIO]] with Has[Unit], Nothing, Unit] = + Module.foo(i) + } + """ + })(isRight(anything)) + }, + testM("generates accessor for varargs functions") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_, _]] { + def varargsFoo(a: Int, b: Int*): F[Unit, Unit] + } + } + + object Check { + def varargsFoo(a: Int, b: Int*): ZIO[Has[Module.Service[IO]], Unit, Unit] = + Module.varargsFoo(a, b: _*) + } + """ + })(isRight(anything)) + }, + testM("compiles when applied to method with simple return type") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_, _]] { + def foo(a: Int): Task[Unit] + } + } + + object Check { + def foo(a: Int): ZIO[Has[Module.Service[IO]], Throwable, Unit] = + Module.foo(a) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for members returning ZManaged") { + assertM(typeCheck { + """ + @accessibleMM[RManaged] + object Module { + trait Service[F[_, _]] { + def managed(s: String): F[Has[Unit], Int] + } + } + + object Check { + def managed(s: String): ZManaged[Has[Module.Service[RManaged]] with Has[Unit], Throwable, Int] = + Module.managed(s) + } + """ + })(isRight(anything)) + }, + testM("generates accessor for service with default method implementations") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_, _]] { + def foo(x: Int): F[Unit, Unit] = foo(x.toString) + def foo(x: String): F[Unit, Unit] + } + } + + object Check { + def foo(x: Int): ZIO[Has[Module.Service[IO]], Unit, Unit] = + Module.foo(x) + def foo(x: String): ZIO[Has[Module.Service[IO]], Unit, Unit] = + Module.foo(x) + } + """.stripMargin + })(isRight(anything)) + }, + testM("generates accessor for service with one type param other than F") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_, _], T] { + val v: F[Int, T] + def f1: F[Int, Unit] + def f2(): F[Int, Unit] + def f3(t: T): F[Int, Unit] + def f4(t: T)(i: Int): F[Int, Unit] + def f5(t: T)(implicit i: Int): F[Int, Unit] + def f6(t: T*): F[Int, Unit] + } + } + + object Check { + def v[T: Tag]: ZIO[Has[Module.Service[IO, T]], Int, T] = + Module.v[T] + def f1[T: Tag]: ZIO[Has[Module.Service[IO, T]], Int, Unit] = + Module.f1[T] + def f2[T: Tag](): ZIO[Has[Module.Service[IO, T]], Int, Unit] = + Module.f2[T]() + def f3[T: Tag](t: T): ZIO[Has[Module.Service[IO, T]], Int, Unit] = + Module.f3[T](t) + def f4[T: Tag](t: T)(i: Int): ZIO[Has[Module.Service[IO, T]], Int, Unit] = + Module.f4[T](t)(i) + def f5[T: Tag](t: T)(implicit i: Int): ZIO[Has[Module.Service[IO, T]], Int, Unit] = + Module.f5[T](t) + def f6[T: Tag](t: T*): ZIO[Has[Module.Service[IO, T]], Int, Unit] = + Module.f6[T](t: _*) + } + """ + })(isRight(anything)) + }, + testM("generates accessor for service with contravariant type param") { + assertM(typeCheck { + """ + @accessibleMM[RIO] + object Module { + trait Service[-A, F[_, _]] { + val v: F[A, Int] + } + } + + object Check { + def v[A: Tag]: ZIO[Has[Module.Service[A, RIO]] with A, Throwable, Int] = + Module.v[A] + } + """ + })(isRight(anything)) + }, + testM("generates accessor for service with two type params and type bounds") { + assertM(typeCheck { + """ + trait Foo + trait Bar + + @accessibleMM[IO] + object Module { + trait Service[T <: Foo, M[_, _], U >: Bar] { + val v: M[U, T] + def f1: M[T, U] + def f2(): M[T, U] + def f3(t: T): M[T, U] + def f4(t: T)(u: U): M[T, U] + def f5(t: T)(implicit u: U): M[T, U] + def f6(t: T*): M[T, U] + } + } + + object Check { + def v[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module.Service[T, IO, U]], U, T] = + Module.v[T, U] + def f1[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module.Service[T, IO, U]], T, U] = + Module.f1[T, U] + def f2[T <: Foo: Tag, U >: Bar: Tag](): ZIO[Has[Module.Service[T, IO, U]], T, U] = + Module.f2[T, U]() + def f3[T <: Foo: Tag, U >: Bar: Tag](t: T): ZIO[Has[Module.Service[T, IO, U]], T, U] = + Module.f3[T, U](t) + def f4[T <: Foo: Tag, U >: Bar: Tag](t: T)(u: U): ZIO[Has[Module.Service[T, IO, U]], T, U] = + Module.f4[T, U](t)(u) + def f5[T <: Foo: Tag, U >: Bar: Tag](t: T)(implicit u: U): ZIO[Has[Module.Service[T, IO, U]], T, U] = + Module.f5[T, U](t) + def f6[T <: Foo: Tag, U >: Bar: Tag](t: T*): ZIO[Has[Module.Service[T, IO, U]], T, U] = + Module.f6[T, U](t: _*) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for ZIO capabilities") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[M[_, _]] { + val static : M[Int, String] + def zeroArgs : M[Int, Int] + def zeroArgsWithParens() : M[Int, Long] + def singleArg(arg1: Int) : M[Int, String] + def multiArgs(arg1: Int, arg2: Long) : M[Int, String] + def multiParamLists(arg1: Int)(arg2: Long) : M[Int, String] + def typedVarargs[T](arg1: Int, arg2: T*) : M[Int, T] + def command(arg1: Int) : M[Int, Unit] + def overloaded(arg1: Int) : M[Int, String] + def overloaded(arg1: Long) : M[Int, String] + } + } + + object Check { + val static : ZIO[Has[Module.Service[IO]], Int, String] = Module.static + def zeroArgs : ZIO[Has[Module.Service[IO]], Int, Int] = Module.zeroArgs + def zeroArgsWithParens() : ZIO[Has[Module.Service[IO]], Int, Long] = Module.zeroArgsWithParens() + def singleArg(arg1: Int) : ZIO[Has[Module.Service[IO]], Int, String] = Module.singleArg(arg1) + def multiArgs(arg1: Int, arg2: Long) : ZIO[Has[Module.Service[IO]], Int, String] = Module.multiArgs(arg1, arg2) + def multiParamLists(arg1: Int)(arg2: Long) : ZIO[Has[Module.Service[IO]], Int, String] = Module.multiParamLists(arg1)(arg2) + def typedVarargs[T](arg1: Int, arg2: T*) : ZIO[Has[Module.Service[IO]], Int, T] = Module.typedVarargs[T](arg1, arg2: _*) + def command(arg1: Int) : ZIO[Has[Module.Service[IO]], Int, Unit] = Module.command(arg1) + def overloaded(arg1: Int) : ZIO[Has[Module.Service[IO]], Int, String] = Module.overloaded(arg1) + def overloaded(arg1: Long) : ZIO[Has[Module.Service[IO]], Int, String] = Module.overloaded(arg1) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for ZManaged capabilities") { + assertM(typeCheck { + """ + @accessibleMM[URManaged] + object Module { + trait Service[M[_, _]] { + val staticManaged : M[Has[Int], String] + def zeroArgsManaged : M[Has[Int], Int] + def zeroArgsTypedManaged[T] : M[Has[Int], T] + def zeroArgsWithParensManaged() : M[Has[Int], Long] + def singleArgManaged(arg1: Int) : M[Has[Int], String] + def multiArgsManaged(arg1: Int, arg2: Long) : M[Has[Int], String] + def multiParamListsManaged(arg1: Int)(arg2: Long) : M[Has[Int], String] + def typedVarargsManaged[T](arg1: Int, arg2: T*) : M[Has[Int], T] + def commandManaged(arg1: Int) : M[Has[Int], Unit] + def overloadedManaged(arg1: Int) : M[Has[Int], String] + def overloadedManaged(arg1: Long) : M[Has[Int], String] + } + } + + object Check { + val staticManaged : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, String] = Module.staticManaged + def zeroArgsManaged : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, Int] = Module.zeroArgsManaged + def zeroArgsTypedManaged[T] : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, T] = Module.zeroArgsTypedManaged[T] + def zeroArgsWithParensManaged() : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, Long] = Module.zeroArgsWithParensManaged() + def singleArgManaged(arg1: Int) : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, String] = Module.singleArgManaged(arg1) + def multiArgsManaged(arg1: Int, arg2: Long) : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, String] = Module.multiArgsManaged(arg1, arg2) + def multiParamListsManaged(arg1: Int)(arg2: Long) : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, String] = Module.multiParamListsManaged(arg1)(arg2) + def typedVarargsManaged[T](arg1: Int, arg2: T*) : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, T] = Module.typedVarargsManaged[T](arg1, arg2: _*) + def commandManaged(arg1: Int) : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, Unit] = Module.commandManaged(arg1) + def overloadedManaged(arg1: Int) : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, String] = Module.overloadedManaged(arg1) + def overloadedManaged(arg1: Long) : ZManaged[Has[Module.Service[URManaged]] with Has[Int], Nothing, String] = Module.overloadedManaged(arg1) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for method capabilities") { + assertM(typeCheck { + """ + @accessibleMM[IO] + object Module { + trait Service[F[_, _]] { + def function(arg1: Int) : String + } + } + + object Check { + def function(arg1: Int) : ZIO[Has[Module.Service[IO]], Throwable, String] = Module.function(arg1) + } + """ + })(isRight(anything)) + }, + testM("preserves type constructor co- and contravariance") { + assertM(typeCheck { + """ + @accessibleMM[URIO] + object Module { + trait Service[F[-_, +_]] { + val v: F[Any, Int] + final val vCov: F[Int, Any] = v + } + } + + object Check { + def assertServiceVariant[S[F[-_, +_]]] = () + assertServiceVariant[Module.Service] + + val v: ZIO[Has[Module.Service[URIO]] with Any, Nothing, Int] = + Module.v + + val vCov: ZIO[Has[Module.Service[URIO]] with Int, Nothing, Any] = + Module.vCov + } + """ + })(isRight(anything)) + }, + testM("preserves Service covariance") { + assertM(typeCheck { + """ + @accessibleMM[URIO] + object Module { + trait Service[+F[_, _]] { + val v: F[Any, Int] + } + + val narrow: Service[URIO] = ??? + val widen: Service[RIO] = narrow + } + + object Check { + def assertServiceVariant[S[+F[_, _]]] = () + assertServiceVariant[Module.Service] + + val v: ZIO[Has[Module.Service[URIO]], Nothing, Int] = + Module.v + } + """ + })(isRight(anything)) + }, + testM("preserves Service contravariance") { + assertM(typeCheck { + """ + @accessibleMM[URIO] + object Module { + trait Service[-F[_, _]] { + val v: F[Any, Int] + } + + val narrow: Service[RIO] = ??? + val widen: Service[URIO] = narrow + } + + object Check { + def assertServiceVariant[S[-F[_, _]]] = () + assertServiceVariant[Module.Service] + + val v: ZIO[Has[Module.Service[URIO]], Nothing, Int] = + Module.v + } + """ + })(isRight(anything)) + }, + // this test mimics the situation when covariant type appears in contravariant position + // in reality, the code will not compile due to true variance check, but in tests `c.typecheck` doesn't check it + testM("fails when contravariant type appears in covariant position") { + assertM(typeCheck { + """ + @accessibleMM[URIO] + object Module { + trait Service[F[+_, +_]] { + val v: F[Int, Int] + } + } + + object Check { + val v: ZIO[Has[Module.Service[URIO]] with AnyVal, Nothing, Int] = + Module.v + } + """ + })(isLeft(anything)) + } + ) + ) +} diff --git a/macros/shared/src/test/scala-2.x/zio/macros/AccessibleMSpec.scala b/macros/shared/src/test/scala-2.x/zio/macros/AccessibleMSpec.scala new file mode 100644 index 000000000000..c3ee262a308d --- /dev/null +++ b/macros/shared/src/test/scala-2.x/zio/macros/AccessibleMSpec.scala @@ -0,0 +1,457 @@ +package zio.macros + +import zio._ +import zio.test.Assertion._ +import zio.test._ + +object AccessibleMSpec extends DefaultRunnableSpec { + + def spec: ZSpec[Environment, Failure] = suite("AccessibleMSpec")( + suite("AccessibleM macro")( + testM("compiles when applied to object with empty Service") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_]] + } + """ + })(isRight(anything)) + }, + testM("fails when applied to object without a Service") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module + """ + })(isLeft(anything)) + }, + testM("fails when applied to trait") { + assertM(typeCheck { + """ + @accessibleM[UIO] + trait Module[F[_]] + """ + })(isLeft(anything)) + }, + testM("fails when applied to class") { + assertM(typeCheck { + """ + @accessibleM[UIO] + class Module[F[_]] + """ + })(isLeft(anything)) + }, + testM("fails when applied to object with Service without type param") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service + } + """ + })(isLeft(anything)) + }, + testM("fails when applied to Service with Service without suitable type param") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_, _]] + } + """ + })(isLeft(anything)) + }, + testM("fails when applied to Service with Service with multiple suitable type params") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_], G[_]] + } + """ + })(isLeft(anything)) + }, + testM("fails when applied to non-ZIO type param") { + assertM(typeCheck { + """ + @accessibleM[List] + object Module { + trait Service[F[_]] + } + """ + })(isLeft(anything)) + }, + testM("generates accessor for values") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_]] { + val foo: F[Unit] + } + } + + object Check { + val foo: ZIO[Has[Module.Service[UIO]], Nothing, Unit] = + Module.foo + } + """ + })(isRight(anything)) + }, + testM("generates accessor for functions") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_]] { + def foo(i: Int): F[Unit] + } + } + + object Check { + def foo(i: Int): ZIO[Has[Module.Service[UIO]], Nothing, Unit] = + Module.foo(i) + } + """ + })(isRight(anything)) + }, + testM("generates accessor for varargs functions") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_]] { + def varargsFoo(a: Int, b: Int*): F[Unit] + } + } + + object Check { + def varargsFoo(a: Int, b: Int*): ZIO[Has[Module.Service[UIO]], Nothing, Unit] = + Module.varargsFoo(a, b: _*) + } + """ + })(isRight(anything)) + }, + testM("compiles when applied to method with simple return type") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_]] { + def foo(a: Int): Task[Unit] + } + } + + object Check { + def foo(a: Int): ZIO[Has[Module.Service[UIO]], Throwable, Unit] = + Module.foo(a) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for members returning ZManaged") { + assertM(typeCheck { + """ + @accessibleM[TaskManaged] + object Module { + trait Service[F[_]] { + def managed(s: String): F[Int] + } + } + + object Check { + def managed(s: String): ZManaged[Has[Module.Service[TaskManaged]], Throwable, Int] = + Module.managed(s) + } + """ + })(isRight(anything)) + }, + testM("generates accessor for service with default method implementations") { + assertM(typeCheck { + """ + @accessibleM[Task] + object Module { + trait Service[F[_]] { + def foo(x: Int): F[Unit] = foo(x.toString) + def foo(x: String): F[Unit] + } + } + + object Check { + def foo(x: Int): ZIO[Has[Module.Service[Task]], Throwable, Unit] = + Module.foo(x) + def foo(x: String): ZIO[Has[Module.Service[Task]], Throwable, Unit] = + Module.foo(x) + } + """.stripMargin + })(isRight(anything)) + }, + testM("generates accessor for service with one type param other than F") { + assertM(typeCheck { + """ + @accessibleM[Task] + object Module { + trait Service[F[_], T] { + val v: F[T] + def f1: F[Unit] + def f2(): F[Unit] + def f3(t: T): F[Unit] + def f4(t: T)(i: Int): F[Unit] + def f5(t: T)(implicit i: Int): F[Unit] + def f6(t: T*): F[Unit] + } + } + + object Check { + def v[T: Tag]: ZIO[Has[Module.Service[Task, T]], Throwable, T] = + Module.v[T] + def f1[T: Tag]: ZIO[Has[Module.Service[Task, T]], Throwable, Unit] = + Module.f1[T] + def f2[T: Tag](): ZIO[Has[Module.Service[Task, T]], Throwable, Unit] = + Module.f2[T]() + def f3[T: Tag](t: T): ZIO[Has[Module.Service[Task, T]], Throwable, Unit] = + Module.f3[T](t) + def f4[T: Tag](t: T)(i: Int): ZIO[Has[Module.Service[Task, T]], Throwable, Unit] = + Module.f4[T](t)(i) + def f5[T: Tag](t: T)(implicit i: Int): ZIO[Has[Module.Service[Task, T]], Throwable, Unit] = + Module.f5[T](t) + def f6[T: Tag](t: T*): ZIO[Has[Module.Service[Task, T]], Throwable, Unit] = + Module.f6[T](t: _*) + } + """ + })(isRight(anything)) + }, + testM("generates accessor for service with covariant type param") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[+A, F[_]] { + val v: F[A] + } + } + + object Check { + def v[A: Tag]: ZIO[Has[Module.Service[A, UIO]], Nothing, A] = + Module.v[A] + } + """ + })(isRight(anything)) + }, + testM("generates accessor for service with two type params and type bounds") { + assertM(typeCheck { + """ + trait Foo + trait Bar + + @accessibleM[Task] + object Module { + trait Service[T <: Foo, M[_], U >: Bar] { + val v: M[T] + def f1: M[U] + def f2(): M[U] + def f3(t: T): M[U] + def f4(t: T)(u: U): M[U] + def f5(t: T)(implicit u: U): M[U] + def f6(t: T*): M[U] + } + } + + object Check { + def v[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module.Service[T, Task, U]], Throwable, T] = + Module.v[T, U] + def f1[T <: Foo: Tag, U >: Bar: Tag]: ZIO[Has[Module.Service[T, Task, U]], Throwable, U] = + Module.f1[T, U] + def f2[T <: Foo: Tag, U >: Bar: Tag](): ZIO[Has[Module.Service[T, Task, U]], Throwable, U] = + Module.f2[T, U]() + def f3[T <: Foo: Tag, U >: Bar: Tag](t: T): ZIO[Has[Module.Service[T, Task, U]], Throwable, U] = + Module.f3[T, U](t) + def f4[T <: Foo: Tag, U >: Bar: Tag](t: T)(u: U): ZIO[Has[Module.Service[T, Task, U]], Throwable, U] = + Module.f4[T, U](t)(u) + def f5[T <: Foo: Tag, U >: Bar: Tag](t: T)(implicit u: U): ZIO[Has[Module.Service[T, Task, U]], Throwable, U] = + Module.f5[T, U](t) + def f6[T <: Foo: Tag, U >: Bar: Tag](t: T*): ZIO[Has[Module.Service[T, Task, U]], Throwable, U] = + Module.f6[T, U](t: _*) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for ZIO capabilities") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[M[_]] { + val static : M[String] + def zeroArgs : M[Int] + def zeroArgsWithParens() : M[Long] + def singleArg(arg1: Int) : M[String] + def multiArgs(arg1: Int, arg2: Long) : M[String] + def multiParamLists(arg1: Int)(arg2: Long) : M[String] + def typedVarargs[T](arg1: Int, arg2: T*) : M[T] + def command(arg1: Int) : M[Unit] + def overloaded(arg1: Int) : M[String] + def overloaded(arg1: Long) : M[String] + } + } + + object Check { + val static : ZIO[Has[Module.Service[UIO]], Nothing, String] = Module.static + def zeroArgs : ZIO[Has[Module.Service[UIO]], Nothing, Int] = Module.zeroArgs + def zeroArgsWithParens() : ZIO[Has[Module.Service[UIO]], Nothing, Long] = Module.zeroArgsWithParens() + def singleArg(arg1: Int) : ZIO[Has[Module.Service[UIO]], Nothing, String] = Module.singleArg(arg1) + def multiArgs(arg1: Int, arg2: Long) : ZIO[Has[Module.Service[UIO]], Nothing, String] = Module.multiArgs(arg1, arg2) + def multiParamLists(arg1: Int)(arg2: Long) : ZIO[Has[Module.Service[UIO]], Nothing, String] = Module.multiParamLists(arg1)(arg2) + def typedVarargs[T](arg1: Int, arg2: T*) : ZIO[Has[Module.Service[UIO]], Nothing, T] = Module.typedVarargs[T](arg1, arg2: _*) + def command(arg1: Int) : ZIO[Has[Module.Service[UIO]], Nothing, Unit] = Module.command(arg1) + def overloaded(arg1: Int) : ZIO[Has[Module.Service[UIO]], Nothing, String] = Module.overloaded(arg1) + def overloaded(arg1: Long) : ZIO[Has[Module.Service[UIO]], Nothing, String] = Module.overloaded(arg1) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for ZManaged capabilities") { + assertM(typeCheck { + """ + @accessibleM[UManaged] + object Module { + trait Service[M[_]] { + val staticManaged : M[String] + def zeroArgsManaged : M[Int] + def zeroArgsTypedManaged[T] : M[T] + def zeroArgsWithParensManaged() : M[Long] + def singleArgManaged(arg1: Int) : M[String] + def multiArgsManaged(arg1: Int, arg2: Long) : M[String] + def multiParamListsManaged(arg1: Int)(arg2: Long) : M[String] + def typedVarargsManaged[T](arg1: Int, arg2: T*) : M[T] + def commandManaged(arg1: Int) : M[Unit] + def overloadedManaged(arg1: Int) : M[String] + def overloadedManaged(arg1: Long) : M[String] + } + } + + object Check { + val staticManaged : ZManaged[Has[Module.Service[UManaged]], Nothing, String] = Module.staticManaged + def zeroArgsManaged : ZManaged[Has[Module.Service[UManaged]], Nothing, Int] = Module.zeroArgsManaged + def zeroArgsTypedManaged[T] : ZManaged[Has[Module.Service[UManaged]], Nothing, T] = Module.zeroArgsTypedManaged[T] + def zeroArgsWithParensManaged() : ZManaged[Has[Module.Service[UManaged]], Nothing, Long] = Module.zeroArgsWithParensManaged() + def singleArgManaged(arg1: Int) : ZManaged[Has[Module.Service[UManaged]], Nothing, String] = Module.singleArgManaged(arg1) + def multiArgsManaged(arg1: Int, arg2: Long) : ZManaged[Has[Module.Service[UManaged]], Nothing, String] = Module.multiArgsManaged(arg1, arg2) + def multiParamListsManaged(arg1: Int)(arg2: Long) : ZManaged[Has[Module.Service[UManaged]], Nothing, String] = Module.multiParamListsManaged(arg1)(arg2) + def typedVarargsManaged[T](arg1: Int, arg2: T*) : ZManaged[Has[Module.Service[UManaged]], Nothing, T] = Module.typedVarargsManaged[T](arg1, arg2: _*) + def commandManaged(arg1: Int) : ZManaged[Has[Module.Service[UManaged]], Nothing, Unit] = Module.commandManaged(arg1) + def overloadedManaged(arg1: Int) : ZManaged[Has[Module.Service[UManaged]], Nothing, String] = Module.overloadedManaged(arg1) + def overloadedManaged(arg1: Long) : ZManaged[Has[Module.Service[UManaged]], Nothing, String] = Module.overloadedManaged(arg1) + } + """ + })(isRight(anything)) + }, + testM("generates accessors for method capabilities") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[_]] { + def function(arg1: Int) : String + } + } + + object Check { + def function(arg1: Int) : ZIO[Has[Module.Service[UIO]], Throwable, String] = Module.function(arg1) + } + """ + })(isRight(anything)) + }, + testM("preserves type constructor covariance") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[+_]] { + val v: F[Int] + final val vCov: F[Any] = v + } + } + + object Check { + def assertServiceVariant[S[F[+_]]] = () + assertServiceVariant[Module.Service] + + val v: ZIO[Has[Module.Service[UIO]], Nothing, Int] = + Module.v + + val vCov: ZIO[Has[Module.Service[UIO]], Nothing, Any] = + Module.vCov + } + """ + })(isRight(anything)) + }, + testM("preserves Service covariance") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[+F[_]] { + val v: F[Int] + } + + val narrow: Service[UIO] = ??? + val widen: Service[Task] = narrow + } + + object Check { + def assertServiceVariant[S[+F[_]]] = () + assertServiceVariant[Module.Service] + + val v: ZIO[Has[Module.Service[UIO]], Nothing, Int] = + Module.v + } + """ + })(isRight(anything)) + }, + testM("preserves Service contravariance") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[-F[_]] { + val v: F[Int] + } + + val narrow: Service[Task] = ??? + val widen: Service[UIO] = narrow + } + + object Check { + def assertServiceVariant[S[-F[_]]] = () + assertServiceVariant[Module.Service] + + val v: ZIO[Has[Module.Service[UIO]], Nothing, Int] = + Module.v + } + """ + })(isRight(anything)) + }, + // this test mimics the situation when covariant type appears in contravariant position + // in reality, the code will not compile due to true variance check, but in tests `c.typecheck` doesn't check it + testM("fails when contravariant type appears in covariant position") { + assertM(typeCheck { + """ + @accessibleM[UIO] + object Module { + trait Service[F[-_]] { + val v: F[AnyVal] + } + } + + object Check { + val v: ZIO[Has[Module.Service[UIO]], Nothing, Int] = + Module.v + } + """ + })(isLeft(anything)) + } + ) + ) +}