Skip to content

Commit 3e745cf

Browse files
committed
substituting type params of enclosing class in macro-read annotations
1 parent 260e164 commit 3e745cf

File tree

8 files changed

+109
-65
lines changed

8 files changed

+109
-65
lines changed

commons-core/src/main/scala/com/avsystem/commons/rest/openapi/WhenAbsentInfo.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ case class WhenAbsentInfo[T](
1111
@infer("for @whenAbsent value: ") asJson: AsRaw[JsonValue, T]
1212
) extends TypedMetadata[T] {
1313
val fallbackValue: Opt[JsonValue] =
14-
Try(annot.value).toOpt.map(asJson.asRaw)
14+
Try(annot.value).fold(_ => Opt.Empty, v => asJson.asRaw(v).opt)
1515
}

commons-core/src/test/scala/com/avsystem/commons/rest/openapi/RestSchemaTest.scala

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.avsystem.commons
22
package rest.openapi
33

4+
import com.avsystem.commons.annotation.AnnotationAggregate
45
import com.avsystem.commons.rest.RestDataCompanion
56
import com.avsystem.commons.rest.openapi.adjusters.description
67
import com.avsystem.commons.serialization.json.JsonStringOutput
7-
import com.avsystem.commons.serialization.{GenCodec, name, transparent}
8+
import com.avsystem.commons.serialization.{GenCodec, name, transparent, whenAbsent}
89
import org.scalatest.FunSuite
910

11+
class customWa[+T](value: => T) extends AnnotationAggregate {
12+
@whenAbsent(value) type Implied
13+
}
14+
1015
class Fuu[T](thing: T)
1116

1217
class RestSchemaTest extends FunSuite {
@@ -21,7 +26,7 @@ class RestSchemaTest extends FunSuite {
2126

2227
@description("kejs klass")
2328
case class KejsKlass(
24-
@name("integer") int: Int,
29+
@name("integer") @customWa(42) int: Int,
2530
@description("serious dependency") dep: Dependency,
2631
@description("serious string") str: Opt[String] = Opt.Empty
2732
)
@@ -35,7 +40,8 @@ class RestSchemaTest extends FunSuite {
3540
| "properties": {
3641
| "integer": {
3742
| "type": "integer",
38-
| "format": "int32"
43+
| "format": "int32",
44+
| "default": 42
3945
| },
4046
| "dep": {
4147
| "description": "serious dependency",
@@ -53,7 +59,6 @@ class RestSchemaTest extends FunSuite {
5359
| }
5460
| },
5561
| "required": [
56-
| "integer",
5762
| "dep"
5863
| ]
5964
|}""".stripMargin)
@@ -70,4 +75,23 @@ class RestSchemaTest extends FunSuite {
7075
| "description": "wrapped string"
7176
|}""".stripMargin)
7277
}
78+
79+
case class GenCC[+T >: Null](@customWa[T](null) value: T)
80+
object GenCC extends RestDataCompanion[GenCC[String]]
81+
82+
test("generic case class") {
83+
println(GenCC.restStructure.asInstanceOf[RestStructure.Record[_]].fields.head.fallbackValue)
84+
85+
assert(schemaStr[GenCC[String]] ==
86+
"""{
87+
| "type": "object",
88+
| "properties": {
89+
| "value": {
90+
| "type": "string",
91+
| "default": null
92+
| }
93+
| }
94+
|}""".stripMargin
95+
)
96+
}
7397
}

commons-macros/src/main/scala/com/avsystem/commons/macros/MacroCommons.scala

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ trait MacroCommons { bundle =>
9999
def indent(str: String, indent: String): String =
100100
str.replaceAllLiterally("\n", s"\n$indent")
101101

102+
def treeAsSeenFrom(tree: Tree, seenFrom: Type): Tree =
103+
if (seenFrom == NoType) tree else {
104+
val res = tree.duplicate
105+
res.foreach { t =>
106+
if (t.tpe != null) {
107+
internal.setType(t, t.tpe.asSeenFrom(seenFrom, seenFrom.typeSymbol))
108+
}
109+
}
110+
res
111+
}
112+
102113
class Annot(annotTree: Tree, val subject: Symbol, val directSource: Symbol, val aggregate: Option[Annot]) {
103114
def aggregationChain: List[Annot] =
104115
aggregate.fold(List.empty[Annot])(a => a :: a.aggregationChain)
@@ -129,7 +140,17 @@ trait MacroCommons { bundle =>
129140
case _ => annotTree
130141
}
131142

132-
def tpe: Type = annotTree.tpe
143+
def tpe: Type =
144+
annotTree.tpe
145+
146+
def symbol: ClassSymbol =
147+
tpe.typeSymbol.asClass
148+
149+
lazy val argsByName: Map[Name, Tree] = {
150+
val Apply(_, args) = tree
151+
val paramNames = primaryConstructorOf(tpe).typeSignature.paramLists.head.map(_.name)
152+
(paramNames zip args).toMap
153+
}
133154

134155
def findArg[T: ClassTag](valSym: Symbol): T =
135156
findArg[T](valSym, abort(s"(bug) no default value for ${tree.tpe} parameter ${valSym.name} provided by macro"))
@@ -149,20 +170,28 @@ trait MacroCommons { bundle =>
149170
case t if param.asTerm.isParamWithDefault && t.symbol.isSynthetic &&
150171
t.symbol.name.decodedName.toString.contains("$default$") => whenDefault
151172
case t if classTag[T] == classTag[Tree] => t.asInstanceOf[T]
152-
case _ =>
153-
abort(s"Expected literal ${classTag[T].runtimeClass} as ${valSym.name} parameter of $clsTpe annotation")
173+
case _ => abort(s"Expected literal ${classTag[T].runtimeClass.getSimpleName} " +
174+
s"as ${valSym.name} parameter of $clsTpe annotation")
154175
}
155176
}
156177
.getOrElse(abort(s"Could not find argument corresponding to constructor parameter ${subSym.name}"))
157178
case _ => abort(s"Not a primary constructor call tree: $tree")
158179
}
159180

181+
private object argsInliner extends Transformer {
182+
override def transform(tree: Tree): Tree = tree match {
183+
case Select(th@This(_), name) if th.symbol == symbol && tree.symbol.asTerm.isParamAccessor =>
184+
argsByName.get(name).map(_.duplicate).getOrElse(tree)
185+
case _ => super.transform(tree)
186+
}
187+
}
188+
160189
lazy val aggregated: List[Annot] = {
161190
if (tpe <:< AnnotationAggregateType) {
162-
val argsInliner = new AnnotationArgInliner(tree)
163191
val impliedMember = tpe.member(TypeName("Implied"))
164-
impliedMember.annotations.map(a =>
165-
new Annot(argsInliner.transform(a.tree), subject, impliedMember, Some(this)))
192+
impliedMember.annotations.map { a =>
193+
new Annot(argsInliner.transform(treeAsSeenFrom(a.tree, tpe)), subject, impliedMember, Some(this))
194+
}
166195
} else Nil
167196
}
168197

@@ -176,21 +205,6 @@ trait MacroCommons { bundle =>
176205
}
177206
}
178207

179-
class AnnotationArgInliner(baseAnnot: Tree) extends Transformer {
180-
private val argsByName: Map[Name, Tree] = {
181-
val Apply(_, args) = baseAnnot
182-
val paramNames = primaryConstructorOf(baseAnnot.tpe).typeSignature.paramLists.head.map(_.name)
183-
(paramNames zip args).toMap
184-
}
185-
186-
override def transform(tree: Tree): Tree = tree match {
187-
case Select(th@This(_), name) if th.symbol == baseAnnot.tpe.typeSymbol
188-
&& tree.symbol.asTerm.isParamAccessor =>
189-
argsByName.get(name).map(_.duplicate).getOrElse(tree)
190-
case _ => super.transform(tree)
191-
}
192-
}
193-
194208
private def orConstructorParam(applyParam: Symbol): Symbol = {
195209
val owner = applyParam.owner
196210
if (owner.name == TermName("apply") && owner.isSynthetic)
@@ -202,19 +216,24 @@ trait MacroCommons { bundle =>
202216
private def maybeWithSuperSymbols(s: Symbol, withSupers: Boolean): Iterator[Symbol] =
203217
if (withSupers) withSuperSymbols(s) else Iterator(s)
204218

205-
def allAnnotations(s: Symbol, tpeFilter: Type, withInherited: Boolean = true, fallback: List[Tree] = Nil): List[Annot] = {
219+
def allAnnotations(s: Symbol, tpeFilter: Type,
220+
seenFrom: Type = NoType, withInherited: Boolean = true, fallback: List[Tree] = Nil): List[Annot] = {
221+
206222
val initSym = orConstructorParam(s)
207223
def inherited(annot: Annotation, superSym: Symbol): Boolean =
208224
!(superSym != initSym && isSealedHierarchyRoot(superSym) && annot.tree.tpe <:< NotInheritedFromSealedTypes)
209225

210226
val nonFallback = maybeWithSuperSymbols(initSym, withInherited)
211-
.flatMap(ss => ss.annotations.filter(inherited(_, ss)).map(a => new Annot(a.tree, s, ss, None)))
227+
.flatMap(ss => ss.annotations.filter(inherited(_, ss))
228+
.map(a => new Annot(treeAsSeenFrom(a.tree, seenFrom), s, ss, None)))
212229

213230
(nonFallback ++ fallback.iterator.map(t => new Annot(t, s, s, None)))
214231
.flatMap(_.withAllAggregated).filter(_.tpe <:< tpeFilter).toList
215232
}
216233

217-
def findAnnotation(s: Symbol, tpe: Type, withInherited: Boolean = true, fallback: List[Tree] = Nil): Option[Annot] = {
234+
def findAnnotation(s: Symbol, tpe: Type,
235+
seenFrom: Type = NoType, withInherited: Boolean = true, fallback: List[Tree] = Nil): Option[Annot] = {
236+
218237
val initSym = orConstructorParam(s)
219238
def find(annots: List[Annot]): Option[Annot] = annots match {
220239
case head :: tail =>
@@ -237,7 +256,8 @@ trait MacroCommons { bundle =>
237256
!(superSym != initSym && isSealedHierarchyRoot(superSym) && annot.tree.tpe <:< NotInheritedFromSealedTypes)
238257

239258
maybeWithSuperSymbols(initSym, withInherited)
240-
.map(ss => find(ss.annotations.filter(inherited(_, ss)).map(a => new Annot(a.tree, s, ss, None))))
259+
.map(ss => find(ss.annotations.filter(inherited(_, ss))
260+
.map(a => new Annot(treeAsSeenFrom(a.tree, seenFrom), s, ss, None))))
241261
.collectFirst { case Some(annot) => annot }
242262
.orElse(find(fallback.map(t => new Annot(t, s, s, None))))
243263
}

commons-macros/src/main/scala/com/avsystem/commons/macros/meta/AdtMetadataMacros.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class AdtMetadataMacros(ctx: blackbox.Context) extends AbstractMacroCommons(ctx)
1717

1818
sealed trait AdtSymbol extends MacroSymbol with SelfMatchedSymbol {
1919
def tpe: Type
20+
def seenFrom: Type = tpe
2021
lazy val symbol: Symbol = tpe.dealias.typeSymbol
2122

2223
def cases: List[AdtSymbol]
@@ -66,14 +67,13 @@ class AdtMetadataMacros(ctx: blackbox.Context) extends AbstractMacroCommons(ctx)
6667
}
6768

6869
class AdtParam(val owner: AdtClass, val symbol: Symbol, val index: Int) extends MacroParam {
70+
def seenFrom: Type = owner.tpe
6971
def shortDescription: String = "ADT parameter"
7072
def description: String = s"$shortDescription $nameStr of ${owner.description}"
7173
}
7274

7375
case class MatchedAdtParam(param: AdtParam, mdParam: AdtParamMetadataParam, indexInRaw: Int) extends MatchedSymbol {
7476
def real: MacroSymbol = param
75-
def annot(tpe: Type): Option[Annot] = findAnnotation(real.symbol, tpe)
76-
def allAnnots(tpe: Type): List[Annot] = allAnnotations(real.symbol, tpe)
7777
def rawName: String = param.nameStr
7878
}
7979

commons-macros/src/main/scala/com/avsystem/commons/macros/meta/MacroMetadatas.scala

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ trait MacroMetadatas extends MacroSymbols {
5353
}
5454

5555
abstract class MetadataParam(val owner: MetadataConstructor, val symbol: Symbol) extends MacroParam {
56+
def seenFrom: Type = owner.ownerType
5657
def shortDescription = "metadata parameter"
5758
def description = s"$shortDescription $nameStr of ${owner.description}"
5859
def pathStr: String = owner.atParam.fold(nameStr)(cp => s"${cp.pathStr}.$nameStr")
@@ -91,27 +92,26 @@ trait MacroMetadatas extends MacroSymbols {
9192
def shortDescription = "metadata class"
9293
def description = s"$shortDescription $ownerType"
9394

94-
def paramByStrategy(paramSym: Symbol, annot: Annot): MetadataParam =
95-
annot.tpe.asSeenFrom(ownerType, ownerType.typeSymbol) match {
96-
case t if t <:< InferAT =>
97-
val clue = annot.findArg[String](InferAT.member(TermName("clue")), "")
98-
new ImplicitParam(this, paramSym, clue)
99-
case t if t <:< ReifyAnnotAT => new ReifiedAnnotParam(this, paramSym)
100-
case t if t <:< ReifyNameAT =>
101-
val useRawName = annot.findArg[Boolean](ReifyNameAT.member(TermName("useRawName")), false)
102-
new ReifiedNameParam(this, paramSym, useRawName)
103-
case t if t <:< IsAnnotatedAT =>
104-
new IsAnnotatedParam(this, paramSym, t.typeArgs.head)
105-
case t => reportProblem(s"metadata param strategy $t is not allowed here")
106-
}
95+
def paramByStrategy(paramSym: Symbol, annot: Annot): MetadataParam = annot.tpe match {
96+
case t if t <:< InferAT =>
97+
val clue = annot.findArg[String](InferAT.member(TermName("clue")), "")
98+
new ImplicitParam(this, paramSym, clue)
99+
case t if t <:< ReifyAnnotAT => new ReifiedAnnotParam(this, paramSym)
100+
case t if t <:< ReifyNameAT =>
101+
val useRawName = annot.findArg[Boolean](ReifyNameAT.member(TermName("useRawName")), false)
102+
new ReifiedNameParam(this, paramSym, useRawName)
103+
case t if t <:< IsAnnotatedAT =>
104+
new IsAnnotatedParam(this, paramSym, t.typeArgs.head)
105+
case t => reportProblem(s"metadata param strategy $t is not allowed here")
106+
}
107107

108108
def compositeConstructor(param: CompositeParam): MetadataConstructor
109109

110110
lazy val paramLists: List[List[MetadataParam]] =
111111
symbol.typeSignatureIn(ownerType).paramLists.map(_.map { ps =>
112112
if (findAnnotation(ps, CompositeAT).nonEmpty)
113113
new CompositeParam(this, ps)
114-
else findAnnotation(ps, MetadataParamStrategyType).map(paramByStrategy(ps, _)).getOrElse {
114+
else findAnnotation(ps, MetadataParamStrategyType, ownerType).map(paramByStrategy(ps, _)).getOrElse {
115115
if (ps.isImplicit) new ImplicitParam(this, ps, "")
116116
else new InvalidParam(this, ps, "no metadata param strategy annotation found")
117117
}
@@ -201,7 +201,7 @@ trait MacroMetadatas extends MacroSymbols {
201201
case ParamArity.Optional(_) =>
202202
Ok(mkOptional(matchedSymbol.annot(annotTpe).map(validatedAnnotTree)))
203203
case ParamArity.Multi(_, _) =>
204-
Ok(mkMulti(allAnnotations(matchedSymbol.real.symbol, annotTpe).map(validatedAnnotTree)))
204+
Ok(mkMulti(matchedSymbol.allAnnots(annotTpe).map(validatedAnnotTree)))
205205
}
206206
}
207207
}

commons-macros/src/main/scala/com/avsystem/commons/macros/meta/MacroSymbols.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ trait MacroSymbols extends MacroCommons {
9090
abstract class MacroSymbol {
9191
def symbol: Symbol
9292
def pos: Position = symbol.pos
93+
def seenFrom: Type
9394
def shortDescription: String
9495
def description: String
9596
def problemStr: String = s"problem with $description"
@@ -118,6 +119,7 @@ trait MacroSymbols extends MacroCommons {
118119

119120
abstract class MacroMethod extends MacroSymbol {
120121
def ownerType: Type
122+
def seenFrom: Type = ownerType
121123

122124
if (!symbol.isMethod) {
123125
abortAt(s"problem with member $nameStr of type $ownerType: it must be a method (def)", pos)
@@ -187,7 +189,7 @@ trait MacroSymbols extends MacroCommons {
187189
def fallbackTag: FallbackTag
188190

189191
def annot(tpe: Type): Option[Annot] =
190-
findAnnotation(symbol, tpe)
192+
findAnnotation(symbol, tpe, seenFrom)
191193

192194
def tagAnnot(tpe: Type): Option[Annot] =
193195
annot(tpe)
@@ -216,7 +218,7 @@ trait MacroSymbols extends MacroCommons {
216218

217219
// returns fallback tag tree only IF it was necessary
218220
def matchTag(realSymbol: MacroSymbol): Res[FallbackTag] = {
219-
val tagAnnot = findAnnotation(realSymbol.symbol, baseTagTpe)
221+
val tagAnnot = findAnnotation(realSymbol.symbol, baseTagTpe, realSymbol.seenFrom)
220222
val fallbackTagUsed = if (tagAnnot.isEmpty) whenUntaggedTag orElse fallbackTag else FallbackTag.Empty
221223
val realTagTpe = tagAnnot.map(_.tpe).getOrElse(NoType) orElse fallbackTagUsed.annotTree.tpe orElse baseTagTpe
222224

@@ -262,16 +264,18 @@ trait MacroSymbols extends MacroCommons {
262264

263265
trait MatchedSymbol {
264266
def real: MacroSymbol
265-
def annot(tpe: Type): Option[Annot]
266-
def allAnnots(tpe: Type): List[Annot]
267267
def rawName: String
268268
def indexInRaw: Int
269+
def fallbackTagUsed: FallbackTag = FallbackTag.Empty
270+
271+
def annot(tpe: Type): Option[Annot] =
272+
findAnnotation(real.symbol, tpe, real.seenFrom, fallback = fallbackTagUsed.asList)
273+
def allAnnots(tpe: Type): List[Annot] =
274+
allAnnotations(real.symbol, tpe, real.seenFrom, fallback = fallbackTagUsed.asList)
269275
}
270276

271277
trait SelfMatchedSymbol extends MacroSymbol with MatchedSymbol {
272278
def real: MacroSymbol = this
273-
def annot(tpe: Type): Option[Annot] = findAnnotation(symbol, tpe)
274-
def allAnnots(tpe: Type): List[Annot] = allAnnotations(symbol, tpe)
275279
def indexInRaw: Int = 0
276280
def rawName: String = nameStr
277281
}

0 commit comments

Comments
 (0)