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

Fix compilation under Scala 3.6 new givens prioritization scheme #238

Merged
merged 1 commit into from
Jul 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class InstancesTests:

@Test
def mapKforSingle: Unit =
val inst = summon[K0.Instances[TypeClass, Single[Int]]]
val inst = summon[K0.ProductInstances[TypeClass, Single[Int]]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the change required? What's wrong with the original implementation?
I think if this test is broken, it would be broken for users too.

Copy link
Contributor Author

@WojciechMazur WojciechMazur Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It all comes to how the implicit are resolved, in short let's start with a description from PR:

Instead of requiring an argument to be most specific, we now require it to be most general while still conforming to the formal parameter.
...
Intuitively, we want to get the closest possible match between required formal parameter type and synthetisized argument. The "most general" rule provides that.

In shapeless there is defined a given method creating both K0.Instances and K0.ProductInstances
Under the old implicit rule, we were getting granted the most specific available type, in our case that's ProductInstances, so even though there exists inline given mkInstances[F[_], T](using gen: Generic[T]): Instances[F, T] it would never be picked, instead, the compiler would always choose inline given mkProductInstances[F[_], T](using gen: ProductGeneric[T]): ProductInstances[F, T]

Now, since 3.6 when asked for K0.Instances and when having an access to instance of this type directly, compiler would choose it instead of using it's more specific subtype.

Because of this single change, the result of the summon has changed. We no longer have access to ProductInstances but to the Instances. Now in the next few lines when we try to use foldLeft on transformed values, we get to the clue of the problem - there is no instance of Foldable instance defined for K0.Instances so the compilation files with type mismatch.

Overall it's very similar to the case from the PR's snippert:

class A
class B extends A
class C extends A

@main def Test = 
   given A = A()
   given B = B()
   def f0(using a: A, b: B) = ()
   f0 // evaluates to f(using given B, given B)
   
   given C = C()
   def f1(using a: A, b: B, c: C) = ()
   f1 // error: ambiguous implicits, compiler cannot choose between given B and given C to use for argument `a`

In the case of shapeless the only difference was that both ProductInstances and CoproductInstances could have never been constructed at the same time, which allowed for compilation.

Probably using method similar toK0.mkInstances[?, ?] after making it transparent inline but not given should be a decent workaround instead of using summon[K0.Instances[?, ?]]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, thanks - foldLeft is missing from Instances. That's fine.

val otherInst = inst.mapK([t] => (tc: TypeClass[t]) => tc.another)
val s = Single(42)

Expand Down