Inconsistency in handling of case classes with private vals
travisbrown opened this issue · comments
This is related to #768 but the problem is different. Suppose we have some case classes:
case class Foo(private val a: Int)
case class Bar(private val a: Int, b: Int)
case class Baz(a: Int, private val b: String)
case class Qux(private val a: Int, b: String)
We get LabelledGeneric
instances for all but the last:
scala> LabelledGeneric[Foo]
res0: shapeless.LabelledGeneric[Foo]{type Repr = Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("a$access$0")],Int] :: shapeless.HNil} = shapeless.LabelledGeneric$$anon$1@eba39b2
scala> LabelledGeneric[Bar]
res1: shapeless.LabelledGeneric[Bar]{type Repr = Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("b")],Int] :: Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("a$access$0")],Int] :: shapeless.HNil} = shapeless.LabelledGeneric$$anon$1@685c20ab
scala> LabelledGeneric[Baz]
res2: shapeless.LabelledGeneric[Baz]{type Repr = Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("a")],Int] :: String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("b$access$1")],String] :: shapeless.HNil} = shapeless.LabelledGeneric$$anon$1@16da3286
scala> LabelledGeneric[Qux]
^
error: could not find implicit value for parameter lgen: shapeless.LabelledGeneric[Qux]
The generalisation seems to be that if the first member is private, and there is another member with a different type, the instance isn't available. The same pattern holds for Generic
.
This inconsistency was showing up in Circe's semiauto
derivation and was reported by @ohze there.
I also think it's related to #768, specifically the logic of alignFieds
is probably tripping on this:
shapeless/core/src/main/scala/shapeless/generic.scala
Lines 809 to 815 in ab5a1a6
Some background:
-
Why can't we just lookup the fields by name?
We want to support byname parameters like this:class Foo(bar0: => String) { lazy val bar: String = bar0 }
-
Why does it have to be so strict?
We want to prevent unsound cases where we have a mismatch in the order of fields with the same type.
But in the case of synthetic accessor methods, the names are different.
Actually it happens even earlier:
/** Sorts the symbols included in this scope so that:
* 1) Symbols appear in the linearization order of their owners.
* 2) Symbols with the same owner appear in same order of their declarations.
* 3) Synthetic members (e.g. getters/setters for vals/vars) might appear in arbitrary order.
*/
def sorted: List[Symbol]