Skip to content

Commit 76ba3f6

Browse files
Fix Scala 3 product (there are some nasty corner-cases that appeared only on Scala.js/Native and some JVMs, so I had to make the implementation not relying on undefined-behavior)
1 parent b53d4d1 commit 76ba3f6

File tree

1 file changed

+33
-15
lines changed

1 file changed

+33
-15
lines changed

chimney-macro-commons/src/main/scala-3/io/scalaland/chimney/internal/compiletime/datatypes/ProductTypesPlatform.scala

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,18 @@ trait ProductTypesPlatform extends ProductTypes { this: DefinitionsPlatform =>
7676
val A = TypeRepr.of[A]
7777
val sym = A.typeSymbol
7878

79-
// case class fields appear once in sym.caseFields as vals and once in sym.declaredMethods as methods
80-
// additionally sometimes they appear twice! once as "val name" and once as "method name " (notice space at the end
81-
// of name). This breaks matching by order (tuples) but has to be fixed in a way that doesn't filter out fields
82-
// for normal cases.
79+
def sameNamedSymbolIn(syms: Set[Symbol]): Symbol => Boolean = {
80+
val names = syms.map(_.name.trim).toSet
81+
sym2 => names(sym2.name.trim)
82+
}
83+
8384
val (argVals, bodyVals) = {
84-
val fields = ListSet.from(
85-
sym.declaredFields.zipWithIndex
85+
// case class fields appear once in sym.caseFields as vals and once in sym.declaredMethods as methods
86+
// additionally sometimes they appear twice! once as "val name" and once as "method name " (notice space at the end
87+
// of name). This breaks matching by order (tuples) but has to be fixed in a way that doesn't filter out fields
88+
// for normal cases.
89+
def sanitize(syms: List[Symbol]): List[Symbol] =
90+
syms.zipWithIndex
8691
.groupBy(_._1.name.trim)
8792
.view
8893
.map {
@@ -93,27 +98,40 @@ trait ProductTypesPlatform extends ProductTypes { this: DefinitionsPlatform =>
9398
.toList
9499
.sortBy(_._2)
95100
.map(_._1)
96-
)
97-
val args = ListSet.from(paramListsOf(A, sym.primaryConstructor).flatten.filter(fields))
98-
val body = fields.filterNot(args)
99-
(args, body)
101+
102+
// Make sure that: we only use public definitions, output is sorted by the order of definition
103+
def sortedPublicUnique(syms: List[Symbol]): ListSet[Symbol] =
104+
ListSet.from(sanitize(syms.filter(isPublic)).sorted)
105+
106+
// To distinct between vals defined in constructor and in body
107+
val isArg = sameNamedSymbolIn(paramListsOf(A, sym.primaryConstructor).flatten.filter(isPublic).toSet)
108+
109+
val caseFields = sortedPublicUnique(sym.caseFields)
110+
111+
// As silly as it looks: when I tried to get rid of caseFields and handle everything with fieldMembers
112+
// the result was really bad. It probably can be done, but it's error prone at best.
113+
val (argFields, bodyFields) =
114+
sortedPublicUnique(sym.fieldMembers.filterNot(sameNamedSymbolIn(caseFields))).partition(isArg)
115+
116+
(caseFields ++ argFields, bodyFields)
100117
}
101118
val accessorsAndGetters = ListSet.from(
102119
sym.methodMembers
103120
.filterNot(_.paramSymss.exists(_.exists(_.isType))) // remove methods with type parameters
104121
.filterNot(isGarbageSymbol)
105122
.filter(isAccessor)
106-
.filterNot(argVals)
107-
.filterNot(bodyVals)
123+
.filter(isPublic)
124+
.filterNot(sameNamedSymbolIn(argVals))
125+
.filterNot(sameNamedSymbolIn(bodyVals))
126+
.sorted
108127
)
109128

110129
val isArgumentField = argVals
111130
val isBodyField = bodyVals
112131
val localDefinitions = (sym.declaredMethods ++ sym.declaredFields).toSet
113132

114-
// if we are taking caseFields but then we also are using ALL fieldMembers shouldn't we just use fieldMembers?
115-
(argVals ++ bodyVals ++ accessorsAndGetters).filter(_.isPublic).map { getter =>
116-
val name = getter.name
133+
(argVals ++ bodyVals ++ accessorsAndGetters).map { getter =>
134+
val name = getter.name.trim
117135
val tpe = ExistentialType(returnTypeOf[Any](A, getter))
118136
def conformToIsGetters = !name.take(2).equalsIgnoreCase("is") || tpe.Underlying <:< Type[Boolean]
119137
name -> tpe.mapK[Product.Getter[A, *]] { implicit Tpe: Type[tpe.Underlying] => _ =>

0 commit comments

Comments
 (0)