Skip to content

Commit 67cf49e

Browse files
committed
Support GenIso for case class with one field
1 parent b0f65f6 commit 67cf49e

File tree

6 files changed

+124
-9
lines changed

6 files changed

+124
-9
lines changed

compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,9 @@ trait TreeOpsImpl extends scala.tasty.reflect.TreeOps with RootPositionImpl with
383383
}
384384

385385
object Ident extends IdentModule {
386+
def apply(tmref: TermRef)(implicit ctx: Context): Ident =
387+
withDefaultPos(implicit ctx => tpd.Ident(tmref))
388+
386389
def copy(original: Tree)(name: String)(implicit ctx: Context): Ident =
387390
tpd.cpy.Ident(original)(name.toTermName)
388391

compiler/src/dotty/tools/dotc/tastyreflect/TypeOrBoundsOpsImpl.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dotty.tools.dotc.tastyreflect
22

33
import dotty.tools.dotc.core.{Contexts, Names, Types}
4+
import dotty.tools.dotc.core.Decorators._
45

56
trait TypeOrBoundsOpsImpl extends scala.tasty.reflect.TypeOrBoundsOps with CoreImpl {
67

@@ -23,6 +24,9 @@ trait TypeOrBoundsOpsImpl extends scala.tasty.reflect.TypeOrBoundsOps with CoreI
2324
if (tpe.classSymbol.exists) Some(tpe.classSymbol.asClass) else None
2425

2526
def typeSymbol(implicit ctx: Context): Symbol = tpe.typeSymbol
27+
28+
def memberType(member: Symbol)(implicit ctx: Context): Type =
29+
member.info.asSeenFrom(tpe, member.owner)
2630
}
2731

2832
def ConstantTypeDeco(x: ConstantType): Type.ConstantTypeAPI = new Type.ConstantTypeAPI {
@@ -181,6 +185,9 @@ trait TypeOrBoundsOpsImpl extends scala.tasty.reflect.TypeOrBoundsOps with CoreI
181185
}
182186

183187
object TermRef extends TermRefModule {
188+
def apply(qual: TypeOrBounds, name: String)(implicit ctx: Context): TermRef =
189+
Types.TermRef(qual, name.toTermName)
190+
184191
def unapply(x: TypeOrBounds)(implicit ctx: Context): Option[(String, TypeOrBounds /* Type | NoPrefix */)] = x match {
185192
case tp: Types.NamedType =>
186193
tp.designator match {

library/src/scala/tasty/reflect/TreeOps.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package scala.tasty
22
package reflect
33

44
trait TreeOps extends Core {
5-
65
// Decorators
76

87
implicit def TreeDeco(tree: Tree): TreeAPI
@@ -249,6 +248,7 @@ trait TreeOps extends Core {
249248
/** Scala term identifier */
250249
val Ident: IdentModule
251250
abstract class IdentModule {
251+
def apply(tmref: TermRef)(implicit ctx: Context): Ident
252252

253253
def copy(original: Tree)(name: String)(implicit ctx: Context): Ident
254254

library/src/scala/tasty/reflect/TypeOrBoundsOps.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ trait TypeOrBoundsOps extends Core {
5555
def widen(implicit ctx: Context): Type
5656
def classSymbol(implicit ctx: Context): Option[ClassSymbol]
5757
def typeSymbol(implicit ctx: Context): Symbol
58+
def memberType(member: Symbol)(implicit ctx: Context): Type
5859
}
5960

6061
val IsType: IsTypeModule
@@ -107,6 +108,7 @@ trait TypeOrBoundsOps extends Core {
107108

108109
val TermRef: TermRefModule
109110
abstract class TermRefModule {
111+
def apply(qual: TypeOrBounds, name: String)(implicit ctx: Context): TermRef
110112
def unapply(typeOrBounds: TypeOrBounds)(implicit ctx: Context): Option[(String, TypeOrBounds /* Type | NoPrefix */)]
111113
}
112114

tests/run-with-compiler/i5941/macro_1.scala

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
abstract class Lens[S, T] {
1+
trait Lens[S, T] {
22
def get(s: S): T
33
def set(t: T, s: S) :S
44
}
@@ -19,15 +19,15 @@ object Lens {
1919

2020

2121
// obj.copy(a = obj.a.copy(b = a.b.copy(c = v)))
22-
def setterBody(obj: Term, value: Term, fields: List[String]): Term = {
22+
def setterBody(obj: Term, value: Term, parts: List[String]): Term = {
2323
// o.copy(field = value)
2424
def helper(obj: Term, value: Term, field: String): Term =
2525
Term.Select.overloaded(obj, "copy", Nil, Term.NamedArg(field, value) :: Nil)
2626

27-
fields match {
27+
parts match {
2828
case field :: Nil => helper(obj, value, field)
29-
case field :: fields =>
30-
helper(obj, setterBody(Term.Select.unique(obj, field), value, fields), field)
29+
case field :: parts =>
30+
helper(obj, setterBody(Term.Select.unique(obj, field), value, parts), field)
3131
}
3232
}
3333

@@ -56,9 +56,9 @@ object Lens {
5656

5757
// exception: getter.unseal.underlyingArgument
5858
getter.unseal match {
59-
case Function(param :: Nil, Path(o, fields)) if o.symbol == param.symbol =>
59+
case Function(param :: Nil, Path(o, parts)) if o.symbol == param.symbol =>
6060
'{
61-
val setter = (t: T) => (s: S) => ~setterBody(('(s)).unseal, ('(t)).unseal, fields).seal[S]
61+
val setter = (t: T) => (s: S) => ~setterBody(('(s)).unseal, ('(t)).unseal, parts).seal[S]
6262
apply(~getter)(setter)
6363
}
6464
case _ =>
@@ -79,4 +79,93 @@ object GenLens {
7979
class MkGenLens[S] {
8080
inline def apply[T](get: => (S => T)): Lens[S, T] = ~Lens.impl('(get))
8181
}
82+
}
83+
84+
trait Iso[S, A] {
85+
def from(a: A): S
86+
def to(s: S): A
87+
}
88+
89+
object Iso {
90+
def apply[S, A](_from: A => S)(_to: S => A): Iso[S, A] = new Iso {
91+
def from(a: A): S = _from(a)
92+
def to(s: S): A = _to(s)
93+
}
94+
95+
def impl[S: Type, A: Type](implicit refl: Reflection): Expr[Iso[S, A]] = {
96+
import refl._
97+
import util._
98+
import quoted.Toolbox.Default._
99+
100+
val tpS = typeOf[S]
101+
val tpA = typeOf[A]
102+
103+
// 1. S must be a case class
104+
// 2. A must be a tuple
105+
// 3. The parameters of S must match A
106+
if (tpS.classSymbol.flatMap(cls => if (cls.flags.is(Flags.Case)) Some(true) else None).isEmpty)
107+
throw new QuoteError("Only support generation for case classes")
108+
109+
val cls = tpS.classSymbol.get
110+
111+
val companion = tpS match {
112+
case Type.SymRef(sym, prefix) => Type.TermRef(prefix, sym.name)
113+
case Type.TypeRef(name, prefix) => Type.TermRef(prefix, name)
114+
}
115+
116+
if (cls.caseFields.size != 1)
117+
throw new QuoteError("Use GenIso.fields for case classes more than one parameter")
118+
119+
val fieldTp = tpS.memberType(cls.caseFields.head)
120+
if (!(fieldTp =:= tpA))
121+
throw new QuoteError(s"The type of case class field $fieldTp does not match $tpA")
122+
123+
'{
124+
// (p: S) => p._1
125+
val to = (p: S) => ~{ Term.Select.unique(('(p)).unseal, "_1").seal[A] }
126+
// (p: A) => S(p)
127+
val from = (p: A) => ~{ Term.Select.overloaded(Term.Ident(companion), "apply", Nil, ('(p)).unseal :: Nil).seal[S] }
128+
apply(from)(to)
129+
}
130+
}
131+
}
132+
133+
object GenIso {
134+
/**
135+
* GenIso[Person, String] ~~>
136+
*
137+
* Iso[Person, String]
138+
* { p => p._1 }
139+
* { p => Person(p) }
140+
*/
141+
inline def apply[S, A]: Iso[S, A] = ~Iso.impl[S, A]
142+
143+
inline def fields[S, A]: Iso[S, A] = ???
144+
inline def unit[S]: Iso[S, Unit] = ???
145+
}
146+
147+
trait Prism[S, A] {
148+
def getOption(s: S): Option[A]
149+
def apply(a: A): S
150+
}
151+
152+
object Prism {
153+
def apply[S, A](getOpt: S => Option[A])(app: A => S): Prism[S, A] = new Prism {
154+
def getOption(s: S): Option[A] = getOpt(s)
155+
def apply(a: A): S = app(a)
156+
}
157+
158+
def impl[S: Type, A: Type](implicit refl: Reflection): Expr[Prism[S, A]] = ???
159+
}
160+
161+
object GenPrism {
162+
/**
163+
* GenPrism[Json, JStr] ~~>
164+
*
165+
* Prism[Json, JStr]{
166+
* case JStr(v) => Some(v)
167+
* case _ => None
168+
* }(jstr => jstr)
169+
*/
170+
inline def apply[S, A]: Prism[S, A] = ~Prism.impl[S, A]
82171
}
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
case class Address(streetNumber: Int, streetName: String)
2-
32
case class Employee(name: String, addr: Address)
43

4+
sealed trait Json
5+
case object JNull extends Json
6+
case class JStr(v: String) extends Json
7+
case class JNum(v: Double) extends Json
8+
case class JObj(v: Map[String, Json]) extends Json
9+
510
object Test {
611
def main(args: Array[String]): Unit = {
712
val len = GenLens[Address](_.streetNumber)
@@ -10,11 +15,20 @@ object Test {
1015
val addr2 = len.set(5, address)
1116
assert(len.get(addr2) == 5)
1217

18+
// a.b.c
1319
val len2 = GenLens[Employee](_.addr.streetNumber)
1420
val employee = Employee("Bob", Address(10, "High Street"))
1521
assert(len2.get(employee) == 10)
1622
val employee2 = len2.set(5, employee)
1723
assert(employee2.name == "Bob")
1824
assert(len2.get(employee2) == 5)
25+
26+
// prism
27+
// val jStr: Prism[Json, JStr] = GenPrism[Json, JStr]
28+
// assert(jStr.getOption(JNum(4.5)) == None)
29+
// assert(jStr.getOption(JStr("hello")) == Some(JStr("hello")))
30+
// assert(jStr(JStr("world")) == JStr("world"))
31+
32+
assert(GenIso[JStr, String].to(JStr("Hello")) == "Hello")
1933
}
2034
}

0 commit comments

Comments
 (0)