Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package io.udash.macros
import com.avsystem.commons.macros.AbstractMacroCommons
import scala.annotation.tailrec
import scala.reflect.macros.blackbox
class PropertyMacros(val ctx: blackbox.Context) extends AbstractMacroCommons(ctx) {
import c.universe._
private final lazy val Package = q"_root_.io.udash.properties"
private final lazy val IsModelPropertyTemplateCls = tq"$Package.IsModelPropertyTemplate"
private final lazy val PropertyCreatorCls = tq"$Package.PropertyCreator"
private final lazy val PropertyCreatorCompanion = q"$Package.PropertyCreator"
private final lazy val ModelPropertyCreatorCls = tq"$Package.ModelPropertyCreator"
private final lazy val ReadablePropertyCls = tq"$Package.single.ReadableProperty"
private final lazy val CastableReadablePropertyCls = tq"$Package.single.CastableReadableProperty"
private final lazy val PropertyCls = tq"$Package.single.Property"
private final lazy val ReadableSeqPropertyCls = tq"$Package.seq.ReadableSeqProperty"
private final lazy val SeqPropertyCls = tq"$Package.seq.SeqProperty"
private final lazy val ReadableModelPropertyCls = tq"$Package.model.ReadableModelProperty"
private final lazy val ModelPropertyCls = tq"$Package.model.ModelProperty"
private final lazy val ModelPropertyImplCls = tq"$Package.model.ModelPropertyImpl"
private final lazy val CastablePropertyCls = tq"$Package.single.CastableProperty"
private final lazy val ModelPropertyMacroApiCls = tq"$Package.model.ModelPropertyMacroApi"
private final lazy val SeqTpe = typeOf[scala.collection.Seq[_]]
private final lazy val TopLevelSymbols = Set(typeOf[Any], typeOf[AnyRef], typeOf[AnyVal], typeOf[Product], typeOf[Equals]).map(_.typeSymbol)
private def fixOverride(s: Symbol) =
if (s.isTerm && s.asTerm.isOverloaded) {
s.alternatives.filterNot(_.isSynthetic).head
} else s
private def withOverrides(s: Symbol) =
s :: s.overrides.map(fixOverride)
private def isFromTopLevelType(symbol: Symbol) =
withOverrides(symbol).exists(TopLevelSymbols contains _.owner)
//Checks, if symbol is abstract method
private def isAbstractMethod(symbol: Symbol): Boolean =
symbol.isAbstract && symbol.isMethod && !symbol.asMethod.isAccessor
//Checks, if symbol is abstract field
private def isAbstractVal(symbol: Symbol): Boolean =
symbol.isAbstract && symbol.isTerm && symbol.asTerm.isVal
//Checks, if symbol is method and takes any parameters
private def takesParameters(symbol: Symbol): Boolean =
symbol.isMethod && symbol.asMethod.paramLists.nonEmpty
//Finds primary constructor of provided type
private def findPrimaryConstructor(tpe: Type): MethodSymbol =
tpe.members.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get
//Returns filtered members without synthetic, top level and constructor of provided type
private def filterMembers(valueType: Type) =
valueType.members
.filterNot(member => member.isSynthetic || isFromTopLevelType(member) || member.isConstructor || member.isType)
private def hasModelPropertyCreator(valueType: Type): Boolean =
c.typecheck(q"implicitly[$ModelPropertyCreatorCls[$valueType]]", silent = true) != EmptyTree
//Checks, if trait is valid ModelProperty template
private def doesMeetTraitModelRequirements(tpe: Type): Boolean = {
val isClass: Boolean = tpe.typeSymbol.isClass
val isTrait = isClass && tpe.typeSymbol.asClass.isTrait
val isNotSealedTrait = isClass && !tpe.typeSymbol.asClass.isSealed
val isNotSeq = !(tpe <:< SeqTpe)
val members = filterMembers(tpe)
val unimplementableTraitMembers = members.collect {
case s if isAbstractMethod(s) && takesParameters(s) => s
}
val doesNotContainVars = !members.exists {
case s if s.isTerm && s.asTerm.isVar => true
case _ => false
}
isTrait && isNotSealedTrait && isNotSeq && unimplementableTraitMembers.isEmpty && doesNotContainVars
}
//Checks, if method or value can be treated as Property in trait based ModelProperty
private def doesMeetTraitModelElementRequirements(s: Symbol): Boolean =
(isAbstractMethod(s) && !takesParameters(s)) || isAbstractVal(s) ||
(s.isAbstract && s.isMethod && s.asMethod.isAccessor && s.asMethod.accessed == NoSymbol) // val in trait in scala 2.11
//Returns filtered members which can be treated as Properties in trait based ModelProperty
private def traitBasedPropertyMembers(tpe: Type): Seq[Symbol] =
filterMembers(tpe).filter(!_.isPrivate)
.filter(doesMeetTraitModelElementRequirements).toSeq
//Returns filtered members which can be treated as Properties in case class based ModelProperty
private def classBasedPropertyMembers(tpe: Type): Seq[Symbol] =
filterMembers(tpe)
.filter(m => !m.isPrivate && !(tpe <:< typeOf[Tuple2[_, _]] && m.name.decodedName.toString == "swap"))
.filter(m => m.isMethod && (m.asMethod.isParamAccessor || m.asMethod.isCaseAccessor)).toSeq
//Checks, if tpe is a class which can be used as ModelProperty template
private def doesMeetClassModelRequirements(tpe: Type): Boolean = {
tpe.typeSymbol.isClass && !tpe.typeSymbol.isAbstract &&
findPrimaryConstructor(tpe).paramLists.size == 1 &&
findPrimaryConstructor(tpe).paramLists.head.nonEmpty &&
findPrimaryConstructor(tpe).isPublic &&
filterMembers(tpe)
.filter(m => !m.isPrivate && !(tpe <:< typeOf[Tuple2[_, _]] && m.name.decodedName.toString == "swap"))
.forall { m =>
if (m.isMethod && m.asMethod.isAccessor && m.asMethod.accessed.isTerm) m.asMethod.accessed.asTerm.isVal
else m.isMethod || m.asTerm.isVal
}
}
private def getModelPath(tree: Tree) = tree match {
case Function(_, path) => path
case _ => c.abort(tree.pos, "Only inline lambdas supported. Please use `subProp(_.path.to.element)`.")
}
def isValidSubproperty(tpe: Type, name: Name): Boolean =
traitBasedPropertyMembers(tpe).map(_.name).contains(name) || classBasedPropertyMembers(tpe).map(_.name).contains(name)
def reifyModelPart[T: c.WeakTypeTag]: c.Tree = {
val valueType = weakTypeOf[T]
val isClass: Boolean = valueType.typeSymbol.isClass
val isTrait = isClass && valueType.typeSymbol.asClass.isTrait
val isNotSealedTrait = isClass && !valueType.typeSymbol.asClass.isSealed
val isNotSeq = !(valueType <:< SeqTpe)
lazy val isValidModelClass: Boolean = doesMeetClassModelRequirements(valueType)
val members = filterMembers(valueType)
val unimplementableTraitMembers = members.collect {
case s if isAbstractMethod(s) && takesParameters(s) => s
}
val doesNotContainVars = !members.exists {
case s if s.isTerm && s.asTerm.isVar => true
case _ => false
}
val isImplementableTrait = isTrait && unimplementableTraitMembers.isEmpty && doesNotContainVars
val propertyElementsToTypeCheck = if (isTrait && isNotSealedTrait && isNotSeq) {
traitBasedPropertyMembers(valueType)
} else if (isValidModelClass) {
classBasedPropertyMembers(valueType)
} else Seq.empty
if (isTrait && isNotSealedTrait && isNotSeq && isImplementableTrait) {
q"""null"""
} else if (isValidModelClass) {
q"""null """
} else {
val isCC = !(isTrait && isNotSealedTrait && isNotSeq && isImplementableTrait)
c.abort(c.enclosingPosition,
s"""
|The type `$valueType` does not meet model part requirements. It must be (not sealed) trait or immutable case class.
|
|Model part checks:
|* for traits:
| * isTrait: $isTrait
| * isImplementableTrait: $isImplementableTrait
| * isNotSealedTrait: $isNotSealedTrait
| * isNotSeq: $isNotSeq
| * doesNotContainVars: $doesNotContainVars
| * unimplementableTraitMembers: ${
if (isTrait && isNotSealedTrait && isNotSeq)
unimplementableTraitMembers
.map(m => s"${m.name}: ${m.typeSignatureIn(valueType).resultType}")
.map(s => s"\n - $s").mkString("")
else "Visible only for traits."
}
|* for case class:
| * isClass: ${valueType.typeSymbol.isClass}
| * isNotAbstract: ${!valueType.typeSymbol.isAbstract}
| * hasOneParamsListInPrimaryConstructor: ${
isCC && findPrimaryConstructor(valueType).paramLists.size == 1
}
| * primaryConstructorIsPublic: ${
isCC && findPrimaryConstructor(valueType).isPublic
}
| * members: ${
if (isCC)
if (propertyElementsToTypeCheck.nonEmpty) {
propertyElementsToTypeCheck
.map(m => s"${m.name}: ${m.typeSignatureIn(valueType).resultType} " +
s"-> isCaseAccessor: ${m.isMethod && m.asMethod.isCaseAccessor};"
).map(s => s"\n - $s").mkString("")
} else "No members found."
else "Visible only for case classes."
}
|
|Use ModelValue.isModelValue[${propertyElementsToTypeCheck.headOption.getOrElse("T")}] to get more details about `isModelValue` check.
|
|""".stripMargin
)
}
}
private def generateModelProperty(tpe: Type): c.Tree = {
def impl(members: Map[TermName, Type], getCreator: Tree): Tree = {
q"""
new $ModelPropertyImplCls[$tpe](prt) {
override protected def initialize(): Unit = {
..${
members.map {
case (name, returnTpe) =>
q"""properties(${name.toString}) = implicitly[$PropertyCreatorCls[$returnTpe]].newProperty(null.asInstanceOf[$returnTpe], this)"""
}
}
}
protected def internalGet: $tpe =
$getCreator
protected def internalSet(newValue: $tpe, withCallbacks: Boolean, force: Boolean): Unit = {
..${
members.map { case (name, returnTpe) =>
val dealiased = returnTpe.map(_.dealias)
q"""
setSubProp(
getSubProperty[$dealiased](${q"_.$name"}, ${name.toString}),
if (newValue != null) newValue.$name else null.asInstanceOf[$dealiased],
withCallbacks, force
)
"""
}
}
}
}
"""
}
if (doesMeetClassModelRequirements(tpe)) {
val order = findPrimaryConstructor(tpe).paramLists.flatten.map(m => m.name.toTermName)
val members = classBasedPropertyMembers(tpe)
.map(m => m.asMethod.name -> m.typeSignatureIn(tpe).resultType).toMap
impl(members,
q"""
new ${tpe.typeSymbol}(
..${
order.map { name =>
val returnTpe = members(name)
val dealiased = returnTpe.map(_.dealias)
q"""getSubProperty[$dealiased](${q"_.$name"}, ${name.toString}).get"""
}
}
)
"""
)
} else {
val members = traitBasedPropertyMembers(tpe).map(method => (method.asMethod.name, method.typeSignatureIn(tpe).resultType))
impl(members.toMap,
q"""
new $tpe {
..${
members.map { case (name, returnTpe) =>
val dealiased = returnTpe.map(_.dealias)
q"""override val $name: $dealiased = getSubProperty[$dealiased](${q"_.$name"}, ${name.toString}).get"""
}
}
}"""
)
}
}
def reifySubProp[A: c.WeakTypeTag, B: c.WeakTypeTag](f: c.Tree)(ev: c.Tree): c.Tree = {
val resultType = weakTypeOf[B]
q"${reifySubProperty[A, B](f)}.asInstanceOf[$PropertyCls[$resultType]]"
}
def reifySubModel[A: c.WeakTypeTag, B: c.WeakTypeTag](f: c.Tree)(ev: c.Tree): c.Tree = {
val resultType = weakTypeOf[B]
q"${reifySubProperty[A, B](f)}.asInstanceOf[$ModelPropertyCls[$resultType]]"
}
private def subSeqValidation(leafTpe: Type): Unit = {
val singleParamSeq: Boolean = {
val dealiased = leafTpe.map(_.dealias)
dealiased.typeConstructor <:< SeqTpe.typeConstructor && dealiased.typeConstructor.typeParams.lengthCompare(1) == 0
}
if (!singleParamSeq) c.abort(c.enclosingPosition, s"$leafTpe is not a valid subSeq type")
}
def reifySubSeq[A: c.WeakTypeTag, B: c.WeakTypeTag, SeqTpe[_]](f: c.Tree)(ev: c.Tree): c.Tree = {
val resultType = weakTypeOf[B]
q"${reifySubProperty[A, B](f, subSeqValidation)}.asInstanceOf[$SeqPropertyCls[$resultType, $PropertyCls[$resultType] with $CastablePropertyCls[$resultType]]]"
}
def reifyRoSubProp[A: c.WeakTypeTag, B: c.WeakTypeTag](f: c.Tree)(ev: c.Tree): c.Tree = {
val resultType = weakTypeOf[B]
q"${reifySubProperty[A, B](f)}.asInstanceOf[$ReadablePropertyCls[$resultType]]"
}
def reifyRoSubModel[A: c.WeakTypeTag, B: c.WeakTypeTag](f: c.Tree)(ev: c.Tree): c.Tree = {
val resultType = weakTypeOf[B]
q"${reifySubProperty[A, B](f)}.asInstanceOf[$ReadableModelPropertyCls[$resultType]]"
}
def reifyRoSubSeq[A: c.WeakTypeTag, B: c.WeakTypeTag](f: c.Tree)(ev: c.Tree): c.Tree = {
val resultType = weakTypeOf[B]
q"${reifySubProperty[A, B](f, subSeqValidation)}.asInstanceOf[$ReadableSeqPropertyCls[$resultType, $CastableReadablePropertyCls[$resultType]]]"
}
private def reifySubProperty[A: c.WeakTypeTag, B: c.WeakTypeTag](f: c.Tree, onLeafType: Type => Unit = _ => ()): c.Tree = {
val model = c.prefix
val modelPath = getModelPath(f)
@tailrec
def checkIfIsValidPath(tree: Tree, onFirstType: Type => Unit): Boolean = {
tree match {
case Select(next@Ident(_), t) if isValidSubproperty(next.tpe, t) =>
onFirstType(next.tpe.member(t).typeSignature.finalResultType)
true
case Select(next, t) if hasModelPropertyCreator(next.tpe.widen) && isValidSubproperty(next.tpe, t) =>
onFirstType(next.tpe.member(t).typeSignature.finalResultType)
checkIfIsValidPath(next, _ => ())
case Select(next, t) =>
c.abort(c.enclosingPosition,
s"""
|The path must consist of ModelProperties and only leaf can be a Property, ModelProperty or SeqProperty.
| * ${next.tpe.widen} ${if (hasModelPropertyCreator(next.tpe.widen)) "is" else "is NOT"} a ModelProperty
| * $t ${if (isValidSubproperty(next.tpe, t)) "is" else "is NOT"} a valid subproperty (abstract val/def for trait based model or constructor element for (case) class based model)
|
|""".stripMargin
)
case _ =>
c.abort(c.enclosingPosition, s"The path must consist of ModelProperties and leaf can be a simple Property, ModelProperty or SeqProperty.")
}
}
checkIfIsValidPath(modelPath, onLeafType)
def parsePath(tree: Tree): List[(Type, TermName)] = {
def symbolPath(t: Tree): List[Symbol] = t match {
case Select(pre, _) => t.symbol :: symbolPath(pre)
case _ => Nil
}
def mkPath(prefix: Type, syms: List[Symbol]): List[(Type, Symbol)] = syms match {
case Nil => Nil
case head :: tail => (prefix, head) :: mkPath(head.typeSignatureIn(prefix), tail)
}
mkPath(weakTypeOf[A], symbolPath(tree).reverse).map {
case (prefixTpe, symbol) => (symbol.typeSignatureIn(prefixTpe).finalResultType, symbol.asTerm.name)
}
}
val parts = parsePath(modelPath)
@tailrec
def genTree(source: List[(Type, TermName)], targetTree: Tree): Tree = source match {
case (resultType, term) :: Nil if hasModelPropertyCreator(resultType) =>
q"""$targetTree.getSubModel[$resultType](${q"_.$term"}, ${term.decodedName.toString})"""
case (resultType, term) :: tail if hasModelPropertyCreator(resultType) =>
genTree(tail, q"""$targetTree.getSubModel[$resultType](${q"_.$term"}, ${term.decodedName.toString}).asInstanceOf[$ModelPropertyMacroApiCls[$resultType]]""")
case (resultType, term) :: _ =>
q"""$targetTree.getSubProperty[$resultType](${q"_.$term"}, ${term.decodedName.toString})"""
case Nil => targetTree
}
val tpe = weakTypeOf[A]
q"${genTree(parts, q"""$model.asInstanceOf[$ModelPropertyMacroApiCls[$tpe]]""")}"
}
def reifyModelPropertyCreator[A: c.WeakTypeTag](ev: c.Tree): c.Tree = {
val tpe = weakTypeOf[A]
q"""{
new $ModelPropertyCreatorCls[$tpe] with $PropertyCreatorCompanion.MacroGeneratedPropertyCreator {
override protected def create(prt: $ReadablePropertyCls[_]): $CastablePropertyCls[$tpe] = {
implicit val ${TermName(c.freshName())}: $ModelPropertyCreatorCls[$tpe] with $PropertyCreatorCompanion.MacroGeneratedPropertyCreator = this
${generateModelProperty(tpe)}
}
}
}"""
}
def checkModelPropertyTemplate[A: c.WeakTypeTag]: c.Tree = {
val tpe = weakTypeOf[A]
if (doesMeetTraitModelRequirements(tpe) || doesMeetClassModelRequirements(tpe)) q"new $IsModelPropertyTemplateCls[$tpe]"
else c.abort(c.enclosingPosition,
s"""
|`$tpe` is not a valid ModelProperty template.
|
|There are two valid model bases:
| * trait (not sealed trait) with following restrictions:
| * it cannot contain vars
| * it can contain implemented vals and defs, but they are not considered as subproperties
| * all abstract vals and defs (without parameters) are considered as subproperties
| * (case) class with following restrictions:
| * it cannot contain vars
| * it can contain implemented vals and defs, but they are not considered as subproperties
| * it cannot have more than one parameters list in primary constructor
| * all elements of primary constructor are considered as subproperties
""".stripMargin
)
}
}