1
1
import scala .quoted ._
2
2
3
3
object 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
- }
11
4
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 )}
14
7
}
15
8
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
+ }
17
13
18
- def toTupleImpl (s : Expr [Selectable ])(given qctx : QuoteContext ): Expr [Tuple ] = {
14
+ private def toTupleImpl (s : Expr [Selectable ])(given qctx : QuoteContext ): Expr [Tuple ] = {
19
15
import qctx .tasty .{given , _ }
20
16
21
17
val repr = s.unseal.tpe.widenTermRefExpr.dealias
22
18
23
19
def rec (tpe : Type ): List [(String , Type )] = {
24
20
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
+
26
32
case _ => Nil
27
33
}
28
34
}
29
35
30
36
def tupleElem (name : String , info : Type ): Expr [Any ] = {
31
37
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] }))
35
40
}
36
41
}
37
42
@@ -40,32 +45,51 @@ object Macro {
40
45
Expr .ofTuple(ret)
41
46
}
42
47
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 ] = {
47
49
import qctx .tasty .{given , _ }
48
50
49
51
val repr = s.unseal.tpe.widenTermRefExpr.dealias
50
52
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 )) = {
52
56
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
59
84
}
60
85
}
61
86
62
- val r = rec(repr)
87
+ val r = rec(repr, Set .empty )
63
88
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
65
90
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}] }
69
93
}
70
94
}
71
95
}
0 commit comments