11import scala .quoted ._
22
33object Macro {
4- case class Record (elems : (String , Any )* ) extends Selectable {
5- def selectDynamic (name : String ): Any = elems.find(_._1 == name).get._2
6- def toTuple = {
7- // todo: unnecessary transformation?
8- Tuple .fromArray(elems.toArray)
9- }
10- }
114
12- object Record {
13- def fromTuple ( t : Tuple ) : Record = Record (t.toArray.toSeq.map(e => e. asInstanceOf [( String , Any )]) : _* )
5+ trait SelectableRecord extends Selectable {
6+ inline def toTuple < : Tuple = $ { toTupleImpl( ' this )}
147 }
158
16- inline def toTuple (s : Selectable ) <: Tuple = $ { toTupleImpl(' s )}
9+ trait SelectableRecordCompanion [T ] {
10+ protected def fromUntypedTuple (elems : (String , Any )* ): T
11+ inline def fromTuple [T <: Tuple ](s : T ) <: Any = $ { fromTupleImpl(' s , ' { (x : Array [(String , Any )]) => fromUntypedTuple(x : _* ) } ) }
12+ }
1713
18- def toTupleImpl (s : Expr [Selectable ])(given qctx : QuoteContext ): Expr [Tuple ] = {
14+ private def toTupleImpl (s : Expr [Selectable ])(given qctx : QuoteContext ): Expr [Tuple ] = {
1915 import qctx .tasty .{given , _ }
2016
2117 val repr = s.unseal.tpe.widenTermRefExpr.dealias
2218
2319 def rec (tpe : Type ): List [(String , Type )] = {
2420 tpe match {
25- case Refinement (parent, name, info : Type ) => (name, info) :: rec(parent)
21+ case Refinement (parent, name, info) =>
22+ info match {
23+ case _ : TypeBounds =>
24+ rec(parent)
25+ case _ : MethodType | _ : PolyType | _ : TypeBounds =>
26+ qctx.warning(s " Ignored $name as a field of the record " , s)
27+ rec(parent)
28+ case info : Type =>
29+ (name, info) :: rec(parent)
30+ }
31+
2632 case _ => Nil
2733 }
2834 }
2935
3036 def tupleElem (name : String , info : Type ): Expr [Any ] = {
3137 val nameExpr = Expr (name)
32- info.seal match {
33- case ' [$qType] =>
34- Expr .ofTuple(Seq (nameExpr, ' {$s.selectDynamic($nameExpr).asInstanceOf [$qType]}))
38+ info.seal match { case ' [$qType] =>
39+ Expr .ofTuple(Seq (nameExpr, ' { $s.selectDynamic($nameExpr).asInstanceOf [$qType] }))
3540 }
3641 }
3742
@@ -40,32 +45,51 @@ object Macro {
4045 Expr .ofTuple(ret)
4146 }
4247
43- // note: T is not used ATM
44- inline def toSelectable [T ](s : Tuple ) <: Any = $ { toSelectableImpl(' s , ' [T ])}
45-
46- def toSelectableImpl [T ](s : Expr [Tuple ], tpe : Type [T ])(given qctx : QuoteContext ): Expr [Any ] = {
48+ private def fromTupleImpl [T : Type ](s : Expr [Tuple ], newRecord : Expr [Array [(String , Any )] => T ])(given qctx : QuoteContext ): Expr [Any ] = {
4749 import qctx .tasty .{given , _ }
4850
4951 val repr = s.unseal.tpe.widenTermRefExpr.dealias
5052
51- def rec (tpe : Type ): List [(String , Type )] = {
53+ def isTupleCons (sym : Symbol ): Boolean = sym.owner == defn.ScalaPackageClass && sym.name == " *:"
54+
55+ def extractTuple (tpe : TypeOrBounds , seen : Set [String ]): (Set [String ], (String , Type )) = {
5256 tpe match {
53- // todo:
54- // check number based on prefix
55- // capture the TupleXX variants in recursion
56- case AppliedType (_, args) => args.map{
57- case AppliedType (_, ConstantType (Constant (name : String )) :: (info : Type ) :: Nil ) => (name, info)
58- }
57+ // Tuple2(S, T) where S must be a constant string type
58+ case AppliedType (parent, ConstantType (Constant (name : String )) :: (info : Type ) :: Nil ) if (parent.typeSymbol == defn.TupleClass (2 )) =>
59+ if seen(name) then
60+ qctx.error(s " Repeated record name: $name" , s)
61+ (seen + name, (name, info))
62+ case _ =>
63+ qctx.error(" Tuple type was not explicit expected `(S, T)` where S is a singleton string" , s)
64+ (seen, (" <error>" , defn.AnyType ))
65+ }
66+ }
67+ def rec (tpe : Type , seen : Set [String ]): List [(String , Type )] = {
68+ if tpe =:= defn.UnitType then Nil
69+ else tpe match {
70+ // head *: tail
71+ case AppliedType (parent, List (head, tail : Type )) if isTupleCons(parent.typeSymbol) =>
72+ val (seen2, head2) = extractTuple(head, seen)
73+ head2 :: rec(tail, seen2)
74+ // Tuple1(...), Tuple2(...), ..., Tuple22(...)
75+ case AppliedType (parent, args) if defn.isTupleClass(parent.typeSymbol) =>
76+ (args.foldLeft((seen, List .empty[(String , Type )])){ case ((seenAcc, acc), arg) =>
77+ val (seen3, arg2) = extractTuple(arg, seenAcc)
78+ (seen3, arg2 :: acc)
79+ })._2
80+ // Tuple
81+ case _ =>
82+ qctx.error(" Tuple type must be of known size" , s)
83+ Nil
5984 }
6085 }
6186
62- val r = rec(repr)
87+ val r = rec(repr, Set .empty )
6388
64- val refinementType = r.foldLeft(' [Record ].unseal.tpe)((acc, e) => Refinement (acc, e._1, e._2)).seal
89+ val refinementType = r.foldLeft(' [T ].unseal.tpe)((acc, e) => Refinement (acc, e._1, e._2)).seal
6590
66- refinementType match {
67- case ' [$qType] =>
68- ' { Record .fromTuple($ {s}).asInstanceOf [$ {qType}] }
91+ refinementType match { case ' [$qType] =>
92+ ' { $newRecord($s.toArray.map(e => e.asInstanceOf [(String , Any )])).asInstanceOf [$ {qType}] }
6993 }
7094 }
7195}
0 commit comments