Skip to content

Commit

Permalink
Code Refactor (#530)
Browse files Browse the repository at this point in the history
Free-Tagless-Module Annotations: Change code

These are some small changes to the implementation of the macro annotations `@tagless` and `@free`, as well as their `@module`. 

* We extract a shared `Clait` class, which represents the data from either a `Trait` or a `Class` in Scalameta.
* We extract from the four annotations the logic to extract that `Clait` from the annotated source code.
* We move to the `Clait` class the logic to generate or pick the kind-1 parametrer `F[_]`.
* We extract a class of `ErrorMessages`, used almost identically in all macros. 
* We add some logic to the syntax `Ops` classes in `ScalametaUtil`.
  • Loading branch information
diesalbla committed Feb 10, 2018
1 parent 6cf4bde commit 3521e0d
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 477 deletions.
@@ -0,0 +1,75 @@
/*
* Copyright 2017-2018 47 Degrees, LLC. <http://www.47deg.com>
*
* 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 freestyle.free.internal

import scala.collection.immutable.Seq
import scala.meta._
import scala.meta.Defn.{Class, Trait, Object}

/* A Clait in this context generalises the shared elements of a Class or a Trait
*/
case class Clait(
mods: Seq[Mod],
name: Type.Name,
tparams: Seq[Type.Param],
ctor: Ctor.Primary,
templ: Template
) {

import ScalametaUtil._

def toTrait: Trait = Trait(mods.filtered, name, tparams, ctor, templ)
def toClass: Class = Class(mods.filtered, name, tparams, ctor, templ)

val allTParams: Seq[Type.Param] = tparams.toList match {
case headParam :: tail if headParam.isKind1 => tparams
case _ => Type.fresh("FF$").paramK :: tparams.toList
}
val allTNames: Seq[Type.Name] = allTParams.map(_.toName)
val headTParam: Type.Param = allTParams.head
val headTName: Type.Name = allTNames.head
val tailTParams: Seq[Type.Param] = allTParams.tail
val tailTNames: Seq[Type.Name] = tailTParams.map(_.toName)


def applyDef: Defn.Def = {
val ev = Term.fresh("ev$")
q"def apply[..$allTParams](implicit $ev: $name[..$allTNames]): $name[..$allTNames] = $ev"
}

}

object Clait {

def apply(cls: Class): Clait = Clait(cls.mods, cls.name, cls.tparams, cls.ctor, cls.templ)
def apply(cls: Trait): Clait = Clait(cls.mods, cls.name, cls.tparams, cls.ctor, cls.templ)

def parse(annotation: String, defn: Any): (Clait, Boolean) = {
val errors = new ErrorMessages(annotation)
import errors._
import ScalametaUtil.isAbstract

defn match {
case cls: Trait => (Clait(cls), true)
case cls: Class if isAbstract(cls) => (Clait(cls), false)
case c: Class /* ! isAbstract */ => abort(s"$invalid in ${c.name}. $abstractOnly")
case Term.Block(Seq(_, c: Object)) => abort(s"$invalid in ${c.name}. $noCompanion")
case _ => abort(s"$unexpected. $abstractOnly")
}
}

}
@@ -0,0 +1,30 @@
/*
* Copyright 2017-2018 47 Degrees, LLC. <http://www.47deg.com>
*
* 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 freestyle.free.internal

class ErrorMessages(annotation: String){

// Messages of error
val invalid = s"Invalid use of `$annotation`"
val abstractOnly = s"The `$annotation` annotation can only be applied to a trait or an abstract class."
val noCompanion = s"The trait or class annotated with `$annotation` must have no companion object."
val onlyReqs = s"In a `$annotation`-annotated trait (or class), all abstract method declarations should be of type FS[_]"
val nonEmpty = s"A `$annotation`-annotated trait or class must have at least one abstract method of type `FS[_]`"
val unexpected = s"Unexpected tree encountered for the `$annotation` annotation."

}

Expand Up @@ -29,14 +29,6 @@ object ScalametaUtil {
}

def toVar(name: Term.Name) = Pat.Var.Term(name)
def toName(par: Term.Param): Term.Name = Term.Name(par.name.value)
def toType(par: Type.Param): Type = Type.Name(par.name.value)

def tyParam(ty: Type.Name): Type.Param =
q"type X[Y]".tparams.head.copy(name = ty) // take Y, replace Y name with tyn

def tyParamK(ty: Type.Name): Type.Param =
q"type X[Y[_]]".tparams.head.copy(name = ty) // take Y[_], replace Y name with Tyn

def tyApply(tyFun: Type, tyArg: Type): Type.Apply = Type.Apply(tyFun, Seq(tyArg))

Expand All @@ -45,10 +37,6 @@ object ScalametaUtil {
case ty => Type.Apply(ty, Seq(tyArg))
}

/* Given a Decl.Def, that represents an abstract method, make a concrete method Defn.Def
* by giving it a Term that serves as its body */
def addBody(decl: Decl.Def, body: Term): Defn.Def =
Defn.Def(decl.mods, decl.name, decl.tparams, decl.paramss, Some(decl.decltpe), body)

def mkObject(
mods: Seq[Mod] = Nil,
Expand All @@ -71,6 +59,27 @@ object ScalametaUtil {
case _ => true
}

def filtered: Seq[Mod] = mods.filter {
case mod"@debug" => false
case mod"@stacksafe" => false
case _ => true
}

def isStackSafe: Boolean = mods.exists {
case mod"@stacksafe" => true
case _ => false
}

def isDebug: Boolean = mods exists {
case mod"@debug" => true
case _ => false
}

}

implicit class TypeOps(val theType: Type) extends AnyVal {
def applyTo(tparams: Seq[Type]): Type =
if (tparams.isEmpty) theType else Type.Apply(theType, tparams)
}

implicit class TypeNameOps(val typeName: Type.Name) extends AnyVal {
Expand All @@ -89,6 +98,8 @@ object ScalametaUtil {
implicit class TermParamOps(val termParam: Term.Param) extends AnyVal {
def addMod(mod: Mod): Term.Param = termParam.copy(mods = termParam.mods :+ mod)

def toName: Term.Name = Term.Name(termParam.name.value)

def isImplicit: Boolean = termParam.mods.exists {
case Mod.Implicit() => true
case _ => false
Expand All @@ -108,16 +119,25 @@ object ScalametaUtil {
def classBoundsToParamTypes: Seq[Type.Apply] = typeParam.cbounds.map { cbound =>
Type.Apply(cbound, Seq(Type.Name(typeParam.name.value)))
}

def isKind1: Boolean =
typeParam.tparams.toList match {
case List(tp) if tp.tparams.isEmpty => true
case _ => false
}

def unboundC: Type.Param = typeParam.copy(cbounds = Nil)
}

implicit class DeclDefOps(val declDef: Decl.Def) extends AnyVal {

def addMod(mod: Mod): Decl.Def = declDef.copy(mods = declDef.mods :+ mod)

def argss: Seq[Seq[Term.Name]] = declDef.paramss.map(_.map(toName))
def argss: Seq[Seq[Term.Name]] = declDef.paramss.map(_.map(_.toName))

def withType(ty: Type) = declDef.copy(decltpe = ty)

/* Build a concrete method Defn.Def by giving the body Term */
def addBody(body: Term): Defn.Def = {
import declDef._
Defn.Def(mods, name, tparams, paramss, Some(decltpe), body)
Expand All @@ -144,5 +164,40 @@ object ScalametaUtil {
case Nil => Nil
case headParam :: tailParams => headParam.addMod( Mod.Implicit() ) :: tailParams
}

def toVals: Seq[Term.Param] = termParams.map(_.addMod(Mod.ValParam()))
}

implicit class TermParamListListOps(val termParamss: Seq[Seq[Term.Param]]) extends AnyVal {

/** Adds the given implicitPars to the list of lists of parameters: if termParamss already
has an implicits params list, it appends implicitPars to it. Otherwise, it adds a new list*/
def addImplicits(implicitPars: Seq[Term.Param]): Seq[Seq[Term.Param]] =
if (implicitPars.isEmpty)
termParamss
else if (termParamss.isEmpty)
Seq(implicitPars.toImplicit)
else {
val lastPars = termParamss.last
if (lastPars.hasImplicit)
termParamss.init ++ Seq(lastPars ++ implicitPars)
else
termParamss ++ Seq( implicitPars.toImplicit)
}

def addEmptyExplicit: Seq[Seq[Term.Param]] = termParamss.toList match {
case Nil => Seq( Seq())
case List(imps) if imps.hasImplicit => Seq( Seq(), imps)
case _ => termParamss
}

}

implicit class TemplateOps(val templ: Template) extends AnyVal {

def addParent( ctorCall: Ctor.Call): Template =
templ.copy( parents = templ.parents ++ Seq(ctorCall) )

}

}

0 comments on commit 3521e0d

Please sign in to comment.