From a8d17a982519600717bc192df7ec6c8bbcee8dcf Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 19 Apr 2024 19:10:43 +0200 Subject: [PATCH] Reduce number of dependenices on Symbol.tree in macros. This kind of usecases is considered as an antipattern, becouse the tree is not quaranteed to exist --- build.sbt | 1 - core/src/main/scala/magnolia1/interface.scala | 2 +- core/src/main/scala/magnolia1/macro.scala | 77 +++++++++++-------- readme.md | 2 +- 4 files changed, 46 insertions(+), 36 deletions(-) diff --git a/build.sbt b/build.sbt index 39d24e6c..79791e0d 100644 --- a/build.sbt +++ b/build.sbt @@ -71,7 +71,6 @@ lazy val test = (projectMatrix in file("test")) .settings(commonSettings) .settings( name := "magnolia-test", - scalacOptions += "-Yretain-trees", projectDependencies ++= Seq( "org.scalameta" %%% "munit" % "1.0.0-M6" ), diff --git a/core/src/main/scala/magnolia1/interface.scala b/core/src/main/scala/magnolia1/interface.scala index ab455273..4afa3dbc 100644 --- a/core/src/main/scala/magnolia1/interface.scala +++ b/core/src/main/scala/magnolia1/interface.scala @@ -35,7 +35,7 @@ object CaseClass: */ def deref(param: Type): PType - /** Requires compilation with `-Yretain-trees` on. + /** Recommended compilation with `-Yretain-trees` on. * @return * default argument value, if any */ diff --git a/core/src/main/scala/magnolia1/macro.scala b/core/src/main/scala/magnolia1/macro.scala index 4a6c2a12..6b5076d3 100644 --- a/core/src/main/scala/magnolia1/macro.scala +++ b/core/src/main/scala/magnolia1/macro.scala @@ -70,10 +70,24 @@ object Macro: .zipWithIndex .map { case (field, i) => exprOfOption { + val defaultMethodName = s"$$lessinit$$greater$$default$$${i + 1}" Expr(field.name) -> tpe.companionClass - .declaredMethod(s"$$lessinit$$greater$$default$$${i + 1}") + .declaredMethod(defaultMethodName) .headOption - .flatMap(_.tree.asInstanceOf[DefDef].rhs) + .map { defaultMethod => + val callDefault = { + val base = Ident(tpe.companionModule.termRef).select(defaultMethod) + val tParams = defaultMethod.paramSymss.headOption.filter(_.forall(_.isType)) + tParams match + case Some(tParams) => TypeApply(base, tParams.map(TypeTree.ref)) + case _ => base + } + + defaultMethod.tree match { + case tree: DefDef => tree.rhs.getOrElse(callDefault) + case _ => callDefault + } + } .map(_.asExprOf[Any]) } } @@ -87,14 +101,10 @@ object Macro: case _ => Nil Expr.ofList { - TypeRepr - .of[T] - .typeSymbol - .caseFields + val typeRepr = TypeRepr.of[T] + typeRepr.typeSymbol.caseFields .map { field => - val tpeRepr = field.tree match - case v: ValDef => v.tpt.tpe - case d: DefDef => d.returnTpt.tpe + val tpeRepr = typeRepr.memberType(field) Expr(field.name) -> getAnnotations(tpeRepr) .filter { a => @@ -110,25 +120,20 @@ object Macro: def repeated[T: Type](using Quotes): Expr[List[(String, Boolean)]] = import quotes.reflect.* - def isRepeated[T](tpeRepr: TypeRepr): Boolean = tpeRepr match - case a: AnnotatedType => - a.annotation.tpe match - case tr: TypeRef => tr.name == "Repeated" - case _ => false - case _ => false - val tpe = TypeRepr.of[T] - val symbol: Option[Symbol] = - if tpe.typeSymbol.isNoSymbol then None else Some(tpe.typeSymbol) - val constr: Option[DefDef] = - symbol.map(_.primaryConstructor.tree.asInstanceOf[DefDef]) - - val areRepeated = constr.toList - .flatMap(_.paramss) - .flatMap(_.params.flatMap { - case ValDef(name, tpeTree, _) => Some(name -> isRepeated(tpeTree.tpe)) - case _ => None - }) + val areRepeated = + if tpe.typeSymbol.isNoSymbol then Nil + else { + val symbol = tpe.typeSymbol + val ctor = symbol.primaryConstructor + for param <- ctor.paramSymss.flatten + yield + val isRepeated = tpe.memberType(param) match { + case AnnotatedType(_, annot) => annot.tpe.typeSymbol == defn.RepeatedAnnot + case _ => false + } + param.name -> isRepeated + } Expr(areRepeated) @@ -209,7 +214,13 @@ object Macro: .filter(filterAnnotation) .map(_.asExpr.asInstanceOf[Expr[Any]]) case _ => - List.empty + // Best effort in case whe -Yretain-trees is not used + // Does not support class parent annotations (in the extends clouse) + tpe.baseClasses + .map(tpe.baseType(_)) + .flatMap(getAnnotations(_)) + .filter(filterAnnotation) + .map(_.asExpr) } } } @@ -247,11 +258,11 @@ object Macro: private def fromDeclarations( from: Symbol ): List[(String, List[Expr[Any]])] = - from.declarations.collect { - case field: Symbol if field.tree.isInstanceOf[ValDef] => - field.name -> field.annotations - .filter(filterAnnotation) - .map(_.asExpr.asInstanceOf[Expr[Any]]) + from.fieldMembers.collect { case field: Symbol => + val annotations = field.annotations ::: field.allOverriddenSymbols.flatMap(_.annotations).toList + field.name -> annotations + .filter(filterAnnotation) + .map(_.asExpr.asInstanceOf[Expr[Any]]) } private def groupByParamName(anns: List[(String, List[Expr[Any]])]) = diff --git a/readme.md b/readme.md index b0647bb6..d4064ade 100644 --- a/readme.md +++ b/readme.md @@ -77,7 +77,7 @@ be automatically available for consideration during contextual search. If you don't want to make the automatic derivation available in the given scope, consider using the `Derivation` trait which provides semi-auto derivation with `derived` method, but also brings some additional limitations. ## Limitations -Accessing default values for case class parameters requires compilation with `-Yretain-trees` on. +For accessing default values for case class parameters we recommend compilation with `-Yretain-trees` on. For a recursive structures it is required to assign the derived value to an implicit variable e.g. ```Scala