1
1
package io .scalaland .catnip .internals
2
2
3
+ import cats .implicits ._
4
+ import cats .data .{ Validated , ValidatedNel }
5
+ import io .scalaland .catnip .internals .DerivedImpl .Config
6
+
3
7
import scala .language .experimental .macros
4
8
import scala .reflect .macros .whitebox .Context
5
- import scala .util .{ Failure , Success , Try }
9
+ import scala .util
6
10
7
- @ SuppressWarnings ( Array ( " org.wartremover.warts.StringPlusAny " ))
8
- private [catnip] class DerivedImpl ( config : Map [ String , ( String , List [ String ])])( val c : Context )( annottees : Seq [Any ])
9
- extends Loggers {
11
+ private [catnip] class DerivedImpl ( mappings : Map [ String , Config ], stubs : Map [ String , Config ])( val c : Context )(
12
+ annottees : Seq [Any ]
13
+ ) extends Loggers {
10
14
11
15
import c .universe ._
12
16
13
17
private type TypeClass = TypeName
14
18
15
19
// TODO: there must be a better way to dealias F[_] type
16
20
private def str2TypeConstructor (typeClassName : String ): Type =
17
- c.typecheck(c.parse(s " null: $typeClassName[Nothing] " )).tpe.dealias.typeConstructor
21
+ util
22
+ .Try {
23
+ // allows using "fake" companion object if a type class is missing one, and use it to redirect to the right type
24
+ val key = c.typecheck(c.parse(s " null: ${typeClassName}.type " )).tpe.dealias.toString
25
+ val stub = stubs.get(key).orElse(stubs.get(key.substring(0 , key.length - " .type" .length))).get.target
26
+ c.typecheck(c.parse(s " null: ${stub}[Nothing] " )).tpe.dealias.typeConstructor
27
+ }
28
+ .orElse(util.Try {
29
+ c.typecheck(c.parse(s " null: ${typeClassName}[Nothing] " )).tpe.dealias.typeConstructor
30
+ })
31
+ .get
18
32
19
33
private def isParametrized (name : TypeName ): String => Boolean =
20
34
s """ (^|[, \\ []) $name([, \\ ]]| $$ ) """ .r.pattern.asPredicate.test _
@@ -27,7 +41,7 @@ private[catnip] class DerivedImpl(config: Map[String, (String, List[String])])(v
27
41
val fType = str2TypeConstructor(typeClassName.toString)
28
42
val implName = TermName (s " _derived_ ${fType.toString.replace('.' , '_' )}" )
29
43
lazy val aType = if (params.nonEmpty) TypeName (tq " $name[.. ${params.map(_.name)}] " .toString) else name
30
- val body = c.parse(s " { ${config (fType.toString)._1 }[ $aType] } " )
44
+ val body = c.parse(s " { ${mappings (fType.toString).target }[ $aType] } " )
31
45
val returnType = tq " $fType[ $aType] "
32
46
// TODO: figure out, why this doesn't work
33
47
// q"""implicit val $implName: $returnType = $body""": ValDef
@@ -38,8 +52,8 @@ private[catnip] class DerivedImpl(config: Map[String, (String, List[String])])(v
38
52
with .. $_ { $_ => .. $_ } """ =>
39
53
withTraceLog(s " Derivation expanded for $name class " ) {
40
54
val fType = str2TypeConstructor(typeClassName.toString)
41
- val otherReqTCs = config (fType.toString)._2 .map(str2TypeConstructor)
42
- val needKind = scala. util.Try (c.typecheck(c.parse(s " null: $fType[List] " ))).isSuccess
55
+ val otherReqTCs = mappings (fType.toString).arguments .map(str2TypeConstructor)
56
+ val needKind = util.Try (c.typecheck(c.parse(s " null: $fType[List] " ))).isSuccess
43
57
val implName = TermName (s " _derived_ ${fType.toString.replace('.' , '_' )}" )
44
58
lazy val aType = TypeName (if (params.nonEmpty) tq " $name[.. ${params.map(_.name)}] " .toString else name.toString)
45
59
lazy val argTypes =
@@ -58,7 +72,7 @@ private[catnip] class DerivedImpl(config: Map[String, (String, List[String])])(v
58
72
}
59
73
.mkString(" " )
60
74
val tcForType = if (needKind) name else aType
61
- val body = c.parse(s " { $suppressUnused${config (fType.toString)._1 }[ $tcForType] } " )
75
+ val body = c.parse(s " { $suppressUnused${mappings (fType.toString).target }[ $tcForType] } " )
62
76
val returnType = tq " $fType[ $tcForType] "
63
77
// TODO: figure out, why this doesn't work
64
78
// if (usedParams.isEmpty) q"""implicit val $implName: $returnType = $body""": ValDef
@@ -106,34 +120,55 @@ private[catnip] class DerivedImpl(config: Map[String, (String, List[String])])(v
106
120
107
121
private [catnip] object DerivedImpl {
108
122
109
- private def loadConfig (name : String ): Either [String , Map [String , (String , List [String ])]] =
110
- Try {
111
- scala.io.Source
112
- .fromURL(getClass.getClassLoader.getResources(name).nextElement)
113
- .getLines
114
- .map(_.trim)
115
- .filterNot(_ startsWith """ ////""" )
116
- .filterNot(_ startsWith """ #""" )
117
- .filterNot(_.isEmpty)
118
- .map { s =>
119
- val kv = s.split('=' )
120
- val typeClass = kv(0 )
121
- val generator :: otherRequiredTC = kv(1 ).split(',' ).toList
122
- typeClass.trim -> (generator -> otherRequiredTC)
123
- }
124
- .toMap
125
- } match {
126
- case Success (value) => Right (value)
127
- case Failure (_ : java.util.NoSuchElementException ) =>
128
- Left (s " Unable to load $name using ${getClass.getClassLoader.toString}" )
129
- case Failure (err : Throwable ) => Left (err.getMessage)
130
- }
123
+ final case class Config (target : String , arguments : List [String ])
124
+
125
+ private def loadConfig (name : String ): ValidatedNel [String , Map [String , Config ]] = {
126
+ val configFiles = getClass.getClassLoader.getResources(name)
127
+ Iterator
128
+ .continually {
129
+ if (configFiles.hasMoreElements) Some (configFiles.nextElement())
130
+ else None
131
+ }
132
+ .takeWhile(_.isDefined)
133
+ .collect {
134
+ case Some (url) =>
135
+ val source = scala.io.Source .fromURL(url)
136
+ try {
137
+ Validated .valid(
138
+ source.getLines
139
+ .map(_.trim)
140
+ .filterNot(_ startsWith raw """ // """ )
141
+ .filterNot(_ startsWith raw """ # """ )
142
+ .filterNot(_.isEmpty)
143
+ .map { s =>
144
+ val kv = s.split('=' )
145
+ val typeClass = kv(0 )
146
+ val generator :: otherRequiredTC = kv(1 ).split(',' ).toList
147
+ typeClass.trim -> (Config (generator, otherRequiredTC))
148
+ }
149
+ .toMap
150
+ )
151
+ } catch {
152
+ case _ : java.util.NoSuchElementException =>
153
+ Validated .invalidNel(s " Unable to load $name using ${getClass.getClassLoader.toString} - failed at $url" )
154
+ case err : Throwable =>
155
+ Validated .invalidNel(err.getMessage)
156
+ } finally {
157
+ source.close()
158
+ }
159
+ }
160
+ .toList
161
+ .sequence
162
+ .map(_.fold(Map .empty[String , Config ])(_ ++ _))
163
+ }
131
164
132
- private val mappingsE : Either [String , Map [String , (String , List [String ])]] = loadConfig(" derive.semi.conf" )
165
+ private val mappingsE : ValidatedNel [String , Map [String , Config ]] = loadConfig(" derive.semi.conf" )
166
+ private val stubsE : ValidatedNel [String , Map [String , Config ]] = loadConfig(" derive.stub.conf" )
133
167
134
168
def impl (c : Context )(annottees : Seq [c.Expr [Any ]]): c.Expr [Any ] =
135
- mappingsE match {
136
- case Right (mappings) => new DerivedImpl (mappings)(c)(annottees).derive().asInstanceOf [c.Expr [Any ]]
137
- case Left (error) => c.abort(c.enclosingPosition, error)
169
+ (mappingsE, stubsE).tupled match {
170
+ case Validated .Valid ((mappings, stubs)) =>
171
+ new DerivedImpl (mappings, stubs)(c)(annottees).derive().asInstanceOf [c.Expr [Any ]]
172
+ case Validated .Invalid (errors) => c.abort(c.enclosingPosition, errors.mkString_(" \n " ))
138
173
}
139
174
}
0 commit comments