Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistency in handling of case classes with private vals #968

Closed
travisbrown opened this issue Feb 24, 2020 · 2 comments · Fixed by #972
Closed

Inconsistency in handling of case classes with private vals #968

travisbrown opened this issue Feb 24, 2020 · 2 comments · Fixed by #972
Labels

Comments

@travisbrown
Copy link
Collaborator

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.

@joroKr21
Copy link
Collaborator

joroKr21 commented Mar 3, 2020

I also think it's related to #768, specifically the logic of alignFieds is probably tripping on this:

def alignFields(tpe: Type, args: List[(TermName, Type)]): Option[List[(TermName, Type)]] = for {
fields <- Option(fieldsOf(tpe))
if fields.size == args.size
if fields.zip(args).forall { case ((fn, ft), (an, at)) =>
(fn == an || at.typeSymbol == definitions.ByNameParamClass) && ft =:= unByName(at)
}
} yield fields

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.

@joroKr21
Copy link
Collaborator

joroKr21 commented Mar 3, 2020

Actually it happens even earlier:

tpe.decls.sorted collect {

    /** 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]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants