Skip to content

Commit a492f50

Browse files
committed
introduced macro-materialized TypeString typeclass
1 parent d499371 commit a492f50

File tree

6 files changed

+295
-2
lines changed

6 files changed

+295
-2
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.avsystem.commons
2+
package misc
3+
4+
import com.avsystem.commons.serialization.{GenCodec, GenKeyCodec, transparent}
5+
6+
/**
7+
* Typeclass that contains string representation of a concrete type. This representation should correctly parse
8+
* and typecheck when used as a type in Scala source code.
9+
*/
10+
@transparent
11+
case class TypeString[T](value: String) extends AnyVal
12+
object TypeString {
13+
def of[T](implicit ts: TypeString[T]): String = ts.value
14+
15+
implicit def materialize[T]: TypeString[T] = macro macros.misc.MiscMacros.typeString[T]
16+
17+
implicit val keyCodec: GenKeyCodec[TypeString[_]] =
18+
GenKeyCodec.create[TypeString[Any]](TypeString(_), _.value).asInstanceOf[GenKeyCodec[TypeString[_]]]
19+
20+
implicit val codec: GenCodec[TypeString[_]] =
21+
GenCodec.materialize[TypeString[Any]].asInstanceOf[GenCodec[TypeString[_]]]
22+
}

commons-core/src/test/scala/com/avsystem/commons/macros/ApplyUnapplyTest.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ object ApplyUnapplyTest {
1111
case class Single(int: Int)
1212
case class Multiple(int: Int, str: String)
1313
case class Gadt[T](t: T, list: List[T], cos: String)
14+
case class Generic[T](value: String)
1415

1516
trait Custom[T]
1617
object Custom {
@@ -25,4 +26,5 @@ object ApplyUnapplyTest {
2526
applierUnapplier[Multiple, (Int, String)]
2627
applierUnapplier[Gadt[Int], (Int, List[Int], String)]
2728
applierUnapplier[Custom[String], String]
29+
applierUnapplier[Generic[String], String]
2830
}

commons-core/src/test/scala/com/avsystem/commons/macros/TreeForTypeTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class TreeForTypeTest {
100100
}
101101
}
102102

103-
object Unrelated {
103+
object UnrelatedTreeForType {
104104

105105
import TreeForTypeTest._
106106

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package com.avsystem.commons
2+
package macros
3+
4+
import com.avsystem.commons.misc.TypeString
5+
import org.scalactic.source.Position
6+
import org.scalatest.FunSuite
7+
8+
import scala.language.experimental.macros
9+
import scala.language.higherKinds
10+
11+
object TypeStringTest {
12+
val x = "x"
13+
14+
type A
15+
16+
type StaticAlias = Int
17+
18+
class OFuu {
19+
val z = "z"
20+
object bar {
21+
val q = "q"
22+
}
23+
}
24+
val fuu = new OFuu
25+
26+
def defineTests[T: TypeString](suite: TypeStringTest): Unit = {
27+
type LocalAlias = Int
28+
29+
import suite.testTypeString
30+
testTypeString[Int]("Int")
31+
testTypeString[LocalAlias]("Int")
32+
testTypeString[StaticAlias]("TypeStringTest.StaticAlias")
33+
testTypeString[Integer]("Integer")
34+
testTypeString[String => Int]("(String) => Int")
35+
testTypeString[String => Int => Double]("(String) => (Int) => Double")
36+
testTypeString[(String => Int) => Double]("((String) => Int) => Double")
37+
testTypeString[() => Int]("() => Int")
38+
testTypeString[(String, Int)]("(String, Int)")
39+
testTypeString[A]("TypeStringTest.A")
40+
testTypeString[T](TypeString.of[T])
41+
testTypeString[List[T]](s"List[${TypeString.of[T]}]")
42+
testTypeString[TypeStringTest#B]("TypeStringTest#B")
43+
testTypeString[x.type]("TypeStringTest.x.type")
44+
testTypeString[fuu.bar.q.type]("TypeStringTest.fuu.bar.q.type")
45+
testTypeString[None.type]("None.type")
46+
testTypeString[this.type]("TypeStringTest.type")
47+
testTypeString[Set.type]("Set.type")
48+
testTypeString[List[Int]]("List[Int]")
49+
testTypeString[Set[_]]("Set[_]")
50+
testTypeString[Set[_ <: String]]("Set[_ <: String]")
51+
testTypeString[Set[T] forSome {type T <: List[T]}]("Set[T] forSome {type T <: List[T]}")
52+
testTypeString[Map[T, T] forSome {type T <: String}]("Map[T, T] forSome {type T <: String}")
53+
testTypeString[Map[K, V] forSome {type K; type V <: List[K]}]("Map[K, V] forSome {type K; type V <: List[K]}")
54+
testTypeString[fu.z.type forSome {val fu: OFuu}]("fu.z.type forSome {val fu: TypeStringTest.OFuu}")
55+
testTypeString[fu.z.type forSome {val fu: OFuu with Singleton}]("fu.z.type forSome {val fu: TypeStringTest.OFuu with Singleton}")
56+
testTypeString[fu.bar.q.type forSome {val fu: OFuu}]("fu.bar.q.type forSome {val fu: TypeStringTest.OFuu}")
57+
testTypeString[AnyRef with Serializable]("AnyRef with Serializable")
58+
testTypeString[AnyRef with Serializable {type A <: String}]("AnyRef with Serializable {type A <: String}")
59+
}
60+
}
61+
62+
class TypeStringTest extends FunSuite {
63+
64+
def testTypeString[T: TypeString](expected: String)(implicit pos: Position): Unit =
65+
test(s"${pos.lineNumber}:$expected") {
66+
assert(TypeString.of[T].replaceAllLiterally("com.avsystem.commons.macros.", "") == expected)
67+
}
68+
69+
TypeStringTest.defineTests[Double](this)
70+
71+
type B
72+
val y = "y"
73+
74+
class Fuu {
75+
val z = "z"
76+
object bar {
77+
val q = "q"
78+
}
79+
}
80+
val fuu = new Fuu
81+
82+
import TypeStringTest.{A, x}
83+
84+
testTypeString[Int]("Int")
85+
testTypeString[Integer]("Integer")
86+
testTypeString[TypeStringTest#B]("TypeStringTest#B")
87+
testTypeString[A]("TypeStringTest.A")
88+
// testTypeString[B]("B")
89+
testTypeString[x.type]("TypeStringTest.x.type")
90+
// testTypeString[y.type]("y.type")
91+
// testTypeString[fuu.bar.q.type]("fuu.bar.q.type")
92+
testTypeString[None.type]("None.type")
93+
// testTypeString[this.type]("this.type")
94+
testTypeString[List[Int]]("List[Int]")
95+
testTypeString[Set[_]]("Set[_]")
96+
testTypeString[Set[_ <: String]]("Set[_ <: String]")
97+
testTypeString[Map[T, T] forSome {type T <: String}]("Map[T, T] forSome {type T <: String}")
98+
// testTypeString[fu.z.type forSome {val fu: Fuu}]("fu.z.type forSome {val fu: Fuu}")
99+
// testTypeString[fu.z.type forSome {val fu: Fuu with Singleton}]("fu.z.type forSome {val fu: Fuu with Singleton}")
100+
// testTypeString[fu.bar.q.type forSome {val fu: Fuu}]("fu.bar.q.type forSome {val fu: Fuu}")
101+
// testTypeString[AnyRef with Serializable]("AnyRef with Serializable")
102+
103+
UnrelatedTypeString.defineTests(this)
104+
}
105+
106+
object UnrelatedTypeString {
107+
108+
import TypeStringTest._
109+
110+
def defineTests[T: TypeString](suite: TypeStringTest): Unit = {
111+
import suite.testTypeString
112+
testTypeString[Int]("Int")
113+
testTypeString[StaticAlias]("TypeStringTest.StaticAlias")
114+
testTypeString[Integer]("Integer")
115+
testTypeString[String => Int]("(String) => Int")
116+
testTypeString[String => Int => Double]("(String) => (Int) => Double")
117+
testTypeString[(String => Int) => Double]("((String) => Int) => Double")
118+
testTypeString[() => Int]("() => Int")
119+
testTypeString[(String, Int)]("(String, Int)")
120+
testTypeString[A]("TypeStringTest.A")
121+
testTypeString[T](TypeString.of[T])
122+
testTypeString[List[T]](s"List[${TypeString.of[T]}]")
123+
testTypeString[TypeStringTest#B]("TypeStringTest#B")
124+
testTypeString[x.type]("TypeStringTest.x.type")
125+
testTypeString[fuu.bar.q.type]("TypeStringTest.fuu.bar.q.type")
126+
testTypeString[None.type]("None.type")
127+
testTypeString[this.type]("UnrelatedTypeString.type")
128+
testTypeString[Set.type]("Set.type")
129+
testTypeString[List[Int]]("List[Int]")
130+
testTypeString[Set[_]]("Set[_]")
131+
testTypeString[Set[_ <: String]]("Set[_ <: String]")
132+
testTypeString[Set[T] forSome {type T <: List[T]}]("Set[T] forSome {type T <: List[T]}")
133+
testTypeString[Map[T, T] forSome {type T <: String}]("Map[T, T] forSome {type T <: String}")
134+
testTypeString[Map[K, V] forSome {type K; type V <: List[K]}]("Map[K, V] forSome {type K; type V <: List[K]}")
135+
testTypeString[fu.z.type forSome {val fu: OFuu}]("fu.z.type forSome {val fu: TypeStringTest.OFuu}")
136+
testTypeString[fu.z.type forSome {val fu: OFuu with Singleton}]("fu.z.type forSome {val fu: TypeStringTest.OFuu with Singleton}")
137+
testTypeString[fu.bar.q.type forSome {val fu: OFuu}]("fu.bar.q.type forSome {val fu: TypeStringTest.OFuu}")
138+
testTypeString[AnyRef with Serializable]("AnyRef with Serializable")
139+
testTypeString[AnyRef with Serializable {type A <: String}]("AnyRef with Serializable {type A <: String}")
140+
}
141+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ trait MacroCommons { bundle =>
3636
final val MapObj = q"$CollectionPkg.immutable.Map"
3737
final val MapCls = tq"$CollectionPkg.immutable.Map"
3838
final val MapSym = typeOf[scala.collection.immutable.Map[_, _]].typeSymbol
39-
final val MaterializedCls = tq"$CommonsPkg.derivation.Materialized"
4039
final val FutureSym = typeOf[scala.concurrent.Future[_]].typeSymbol
4140
final val OptionClass = definitions.OptionClass
4241
final val ImplicitsObj = q"$CommonsPkg.misc.Implicits"

commons-macros/src/main/scala/com/avsystem/commons/macros/misc/MiscMacros.scala

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,133 @@ class MiscMacros(ctx: blackbox.Context) extends AbstractMacroCommons(ctx) {
7676
case t =>
7777
c.abort(t.pos, "Expected string literal here")
7878
}
79+
80+
def typeString[T: WeakTypeTag]: Tree = {
81+
val tpe = weakTypeOf[T]
82+
typeStringParts(tpe) match {
83+
case List(Select(pre, TermName("value"))) => pre
84+
case trees => q"$CommonsPkg.misc.TypeString[$tpe](${mkStringConcat(trees)})"
85+
}
86+
}
87+
88+
def typeStringParts(tpe: Type): List[Tree] = {
89+
val resultTpe = getType(tq"$CommonsPkg.misc.TypeString[$tpe]")
90+
c.inferImplicitValue(resultTpe, withMacrosDisabled = true) match {
91+
case EmptyTree => mkTypeString(tpe)
92+
case tree => List(q"$tree.value")
93+
}
94+
}
95+
96+
def mkStringConcat(trees: List[Tree]): Tree = trees match {
97+
case Nil => StringLiteral("")
98+
case single :: Nil => single
99+
case StringLiteral(str1) :: StringLiteral(str2) :: tail =>
100+
mkStringConcat(StringLiteral(str1 + str2) :: tail)
101+
case head :: tail =>
102+
q"$head+${mkStringConcat(tail)}"
103+
}
104+
105+
def join(trees: List[List[Tree]], sep: String): List[Tree] = trees match {
106+
case Nil => Nil
107+
case List(single) => single
108+
case head :: tail => head ::: lit(sep) :: join(tail, sep)
109+
}
110+
111+
def lit(str: String): Tree =
112+
StringLiteral(str)
113+
114+
def mkNameString(name: Name): String =
115+
showCode(Ident(name))
116+
117+
def isRefTo(quantified: Symbol, arg: Type): Boolean = arg match {
118+
case TypeRef(NoPrefix, `quantified`, Nil) => true
119+
case _ => false
120+
}
121+
122+
def areIndependent(quantified: List[Symbol]): Boolean =
123+
quantified.forall(first => quantified.forall(second => !first.typeSignature.contains(second)))
124+
125+
def mkTypeDefString(s: Symbol, wildcard: Boolean): List[Tree] = s match {
126+
case ExistentialSingleton(_, name, sig) =>
127+
lit(s"val ${mkNameString(name)}: ") :: mkTypeString(sig)
128+
case _ =>
129+
lit(if (wildcard) "_" else s"type ${mkNameString(s.name)}") :: mkTypeString(s.typeSignature)
130+
}
131+
132+
private val autoImported: Set[Symbol] = Set(
133+
definitions.ScalaPackage,
134+
definitions.JavaLangPackage,
135+
definitions.PredefModule
136+
)
137+
138+
def isStaticPrefix(pre: Type): Boolean = pre match {
139+
case SingleType(ppre, _) => isStaticPrefix(ppre)
140+
case ThisType(sym) => sym.isStatic && sym.isModuleClass
141+
case TypeRef(ppre, sym, Nil) => sym.isStatic && sym.isModuleClass && isStaticPrefix(ppre)
142+
case _ => false
143+
}
144+
145+
def mkTypeString(tpe: Type): List[Tree] = tpe match {
146+
case _ if tpe.typeSymbol == definitions.AnyRefClass => List(lit("AnyRef"))
147+
case TypeRef(NoPrefix, ExistentialSingleton(_, name, _), Nil) =>
148+
List(lit(mkNameString(name)))
149+
case TypeRef(_, sym, args) if definitions.FunctionClass.seq.contains(sym) =>
150+
val fargs = args.init
151+
val fres = args.last
152+
lit("(") :: join(fargs.map(typeStringParts), ", ") ::: lit(") => ") :: typeStringParts(fres)
153+
case TypeRef(_, sym, args) if definitions.TupleClass.seq.contains(sym) =>
154+
lit("(") :: join(args.map(typeStringParts), ", ") ::: lit(")") :: Nil
155+
case TypeRef(pre, sym, args) if !sym.isParameter =>
156+
val dealiased = tpe.dealias
157+
if (dealiased.typeSymbol != sym && !isStaticPrefix(pre)) {
158+
mkTypeString(dealiased)
159+
}
160+
else {
161+
val argsReprs =
162+
if (args.isEmpty) Nil
163+
else lit("[") +: join(args.map(typeStringParts), ", ") :+ lit("]")
164+
mkTypePrefix(pre) ::: lit(mkNameString(sym.name)) :: argsReprs
165+
}
166+
case SingleType(_, _) =>
167+
mkTypePrefix(tpe) :+ lit("type")
168+
case ThisType(sym) if sym.isStatic && sym.isModuleClass =>
169+
List(lit(mkStaticPrefix(sym) + "type"))
170+
case ExistentialType(quantified, TypeRef(pre, sym, args))
171+
if quantified.corresponds(args)(isRefTo) && quantified.forall(s => !pre.contains(s)) && areIndependent(quantified) =>
172+
val wildcards = lit("[") +: join(quantified.map(mkTypeDefString(_, wildcard = true)), ", ") :+ lit("]")
173+
mkTypePrefix(pre) ::: lit(mkNameString(sym.name)) :: wildcards
174+
case ExistentialType(quantified, underlying) =>
175+
val typeDefs = join(quantified.map(mkTypeDefString(_, wildcard = false)), "; ")
176+
typeStringParts(underlying) ::: lit(" forSome {") :: typeDefs ::: lit("}") :: Nil
177+
case TypeBounds(lo, hi) =>
178+
val loRepr =
179+
if (lo =:= typeOf[Nothing]) Nil
180+
else lit(" >: ") :: typeStringParts(lo)
181+
val hiRepr =
182+
if (hi =:= typeOf[Any]) Nil
183+
else lit(" <: ") :: typeStringParts(hi)
184+
loRepr ++ hiRepr
185+
case RefinedType(bases, scope) if scope.forall(_.isType) =>
186+
val basesRepr = join(bases.map(typeStringParts), " with ")
187+
val scopeRepr =
188+
if (scope.isEmpty) Nil
189+
else lit(" {") :: join(scope.map(mkTypeDefString(_, wildcard = false)).toList, "; ") ::: lit("}") :: Nil
190+
basesRepr ++ scopeRepr
191+
case _ =>
192+
abort(s"Could not find nor materialize TypeString for $tpe")
193+
}
194+
195+
def mkStaticPrefix(sym: Symbol): String =
196+
if (sym == rootMirror.RootClass) ""
197+
else mkStaticPrefix(sym.owner) + mkNameString(sym.name) + "."
198+
199+
def mkTypePrefix(tpe: Type): List[Tree] = tpe match {
200+
case t if autoImported.contains(t.termSymbol) => Nil
201+
case NoPrefix => Nil
202+
case SingleType(pkg, sym) if sym.name == termNames.PACKAGE && pkg.typeSymbol.isPackageClass => mkTypePrefix(pkg)
203+
case SingleType(pre, sym) => mkTypePrefix(pre) :+ lit(mkNameString(sym.name) + ".")
204+
case ThisType(sym) if sym.isStatic && sym.isModuleClass => List(lit(mkStaticPrefix(sym)))
205+
case TypeRef(NoPrefix, ExistentialSingleton(_, name, _), Nil) => List(lit(mkNameString(name) + "."))
206+
case _ => mkTypeString(tpe) :+ lit(if (tpe.typeSymbol.isModuleClass || tpe.termSymbol != NoSymbol) "." else "#")
207+
}
79208
}

0 commit comments

Comments
 (0)