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

[Experiment] Drop prioritization of givens over implicits #21273

Closed
wants to merge 13 commits into from

Conversation

EugeneFlesselle
Copy link
Contributor

@EugeneFlesselle EugeneFlesselle commented Jul 25, 2024

Should fix #21264 although that has not been tested yet

@EugeneFlesselle
Copy link
Contributor Author

@WojciechMazur could you test if the changes here drop the warnings from #21264 (comment) ?
Note that even if it should solve those, it may likely also incur new warnings (including in other projects).

@EugeneFlesselle
Copy link
Contributor Author

EugeneFlesselle commented Jul 25, 2024

@odersky Unfortunately, there are already errors in the community-build C, e.g.:

[error] -- [E172] Type Error: /__w/scala3/scala3/community-build/community-projects/scalaz/core/src/main/scala/scalaz/Adjunction.scala:120:46 
[error] 120 |    new Adjunction[Writer[E, *], Reader[E, *]] {
[error]     |                                              ^
[error]     |Ambiguous given instances: both method writerTraverse in class WriterTInstances0 and method writerFunctor in class WriterTInstances14 match type scalaz.Functor[[_] =>> scalaz.Writer[E, _]] of parameter F of constructor Adjunction in class Adjunction

[error] -- [E007] Type Mismatch Error: /__w/scala3/scala3/community-build/community-projects/scala-collection-compat/compat/src/test/scala/test/scala/collection/BuildFromTest.scala:124:39 
[error] 124 |    val xs6: TreeMap[String, String] = xs5
[error]     |                                       ^^^
[error]     |          Found:    (xs5 : Map[String, String])
[error]     |          Required: scala.collection.immutable.TreeMap[String, String]
[error]     |
[error]     | longer explanation available when compiling with `-explain`

[error] -- [E172] Type Error: /__w/scala3/scala3/community-build/community-projects/parboiled2/parboiled-core/src/test/scala/org/parboiled2/ActionSpec.scala:40:32 
[error] 40 |      "`test`" - new TestParser0 {
[error]    |                                ^
[error]    |Ambiguous given instances: both method hnil in object Unpack and object HNilUnpack in object Unpack match type org.parboiled2.support.Unpack.Aux[org.parboiled2.support.hlist.HNil, Unit] of parameter unpack of constructor TestParser in class TestParser

[error] -- [E172] Type Error: /__w/scala3/scala3/community-build/community-projects/parboiled2/parboiled-core/src/test/scala/org/parboiled2/RunningSpec.scala:38:17 
[error] 38 |        p.A.run() ==> Success(())
[error]    |                 ^
[error]    |No best given instance of type org.parboiled2.Parser.DeliveryScheme[org.parboiled2.support.hlist.HNil] was found for parameter scheme of method run in trait RuleRunnable.
[error]    |I found:
[error]    |
[error]    |    org.parboiled2.Parser.DeliveryScheme.Try[org.parboiled2.support.hlist.HNil, Out]
[error]    |      (
[error]    |      /* ambiguous: both method hnil in object Unpack and object HNilUnpack in object Unpack match type org.parboiled2.support.Unpack.Aux[org.parboiled2.support.hlist.HNil, Out] */
[error]    |        summon[
[error]    |          org.parboiled2.support.Unpack.Aux[org.parboiled2.support.hlist.HNil, Out]]
[error]    |      )
[error]    |
[error]    |But both method hnil in object Unpack and object HNilUnpack in object Unpack match type org.parboiled2.support.Unpack.Aux[org.parboiled2.support.hlist.HNil, Out].

[error] -- [E007] Type Mismatch Error: /__w/scala3/scala3/community-build/community-projects/play-json/play-json/js/src/test/scala/JsonSpec.scala:38:26 
[error] 38 |        "key5" -> Json.obj(
[error]    |                  ^
[error]    |Found:    play.api.libs.json.JsObject
[error]    |Required: play.api.libs.json.Json.JsValueWrapper
[error]    |Note that implicit conversions cannot be applied because they are ambiguous;
[error]    |both method writes in object JsObject and method jsValueWrites in trait DefaultWrites match type play.api.libs.json.Writes[play.api.libs.json.JsObject]
[error] 39 |          "key6" -> "こんにちは",
[error] 40 |          "key7" -> BigDecimal("12345678901234567000")
[error] 41 |        )
[error]    |
[error]    | longer explanation available when compiling with `-explain`
[error] -- [E007] Type Mismatch Error: /__w/scala3/scala3/community-build/community-projects/play-json/play-json/js/src/test/scala/JsonSpec.scala:56:26 

@odersky odersky force-pushed the given-priority branch 2 times, most recently from 90ec3ed to a5b816b Compare July 26, 2024 14:55
@odersky
Copy link
Contributor

odersky commented Jul 28, 2024

@WojciechMazur Can we test this PR with the openCB? It is a modified implicit priority scheme.

On the one hand. we also reverse priority for implicit/implicit pairs, which was not the case before. The advantage is that we do the priority switch in one go. If a library switches from implicits to givens in the future there is no danger of an inadvertent change in resolution. There is one caveat: We still fall back to the old priority rules for implicit/implicit pairs if the new ones would give an ambiguity error. So changing these implicit/implicit pairs to givens risks causing ambiguity errors. But it does not risk a change in the resolved given.

On the other hand we add a tweak where in the case of ambiguities owner ranking trumps type ranking. This helps since there are libraries that explicitly prioritize their implicits by placing them into different classes of a linear hierarchy. If the type score now goes against that we honor the explicit prioritization according to owner instead of reporting an ambiguity.

@EugeneFlesselle
Copy link
Contributor Author

@odersky I was in the process of writing a review, but ended up pushing two small changes instead:

  • the first being a small optimization,
  • the second (and more notable one) being that it seems we may not need the (2nd) owner score disambiguation scheme after all,

I hope that's okay.

The changes look good to me otherwise, hopefully we get positive results from the openCB

@WojciechMazur
Copy link
Contributor

I've started the OpenCB based on last commit -e1cc065 I'll report results when it done

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

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

@EugeneFlesselle I don't think the optimization is worth it here (not that it matters much either way). It should be very rare to have an ambiguous implicit/implicit pair. On the other hand, a local lazy val requires an allocation even if it is never used (which will be the case for all new code past 3.6 which uses only gives). So since the benefit is dubious or even negative, I don't think it's worthwhile complicating the logic here.

EDIT: In fact looking at it more I find the revised logic clearer than the original one. So let's keep it, a single allocation should not matter anyway here.

@odersky
Copy link
Contributor

odersky commented Jul 29, 2024

I believe we still want the ownerscore disambiguation because this gives us a way to upgrade implicit hierarchies to given hierarchies. Right now, the ownerscre disambiguation was not needed since all owner hierarchies we have seen were over implicits so the second scheme for disambiguating implicit/implicit pairs worked in its place. But this will change when those implicits are changed to givens. Then the ownerscore disambiguation becomes essential.

@EugeneFlesselle
Copy link
Contributor Author

EugeneFlesselle commented Jul 29, 2024

I believe we still want the ownerscore disambiguation because this gives us a way to upgrade implicit hierarchies to given hierarchies. Right now, the ownerscre disambiguation was not needed since it all owner hierarchies we have seen were over implicits so the second scheme for disambiguating implicit/implicit pairs worked in its place. But this will change when those implicits are changed to givens. Then the ownerscore disambiguation becomes essential.

In that case, I don't think the previous version addressed that case as we were under the case matching ownerScore to 0.

EDIT: in fact, I think the ownerScore match may be inexhaustive

@EugeneFlesselle
Copy link
Contributor Author

@EugeneFlesselle I don't think the optimization is worth it here (not that it matters much either way). It should be very rare to have an ambiguous implicit/implicit pair. On the other hand, a local lazy val requires an allocation even if it is never used (which will be the case for all new code past 3.6 which uses only gives). So since the benefit is dubious or even negative, I don't think it's worthwhile complicating the logic here.

EDIT: In fact looking at it more I find the revised logic clearer than the original one. So let's keep it, a single allocation should not matter anyway here.

Either way, dropping the commit is completely fine with me

@odersky
Copy link
Contributor

odersky commented Jul 29, 2024

@EugeneFlesselle @bracevac

Once we have the results from the OpenCB we might want to explore ways how to mitigate the breakage. Say we have a library with implicits whose priority changes. We find out in a test of the library or in an application using the library. We can fix this on ether the library side or the application side.

  • On the library side: Prioritize explicitly using owner score.
  • On the application side: Provide an explicit argument fixing the old choice.

One technique which might help would be to offer a rewrite rule in 3.5 or 3.6-migration that adds an explicit argument according to the old rules if a priority change is observed.

@odersky
Copy link
Contributor

odersky commented Jul 29, 2024

In that case, I don't think the previous version addressed that case as we were under the case matching ownerScore to 0.

Indeed. I now pushed a commit that should fix this problem.

…ion rules

That way, we see the difference if something changes.
@WojciechMazur
Copy link
Contributor

I'm having a tough time comparing the results of the OpenCB due to the merge of #21257 which (probably) caused failures in ~90 projects in the last nightly. I might need to create a dedicated branch without this change and run build again so we would be able to gather any decent conclusions. We might try to run them with -source:3.5-migration to mitigate the problems with using clouses, but it would also affect results of given prioritization (especially the warnings instead of hard errors that are easy to miss)

@odersky
Copy link
Contributor

odersky commented Jul 29, 2024

I am shortly going to push a fix & analysis for the failing StringFormatterTest.

Once we saw what got changed we got priority errors like this:
```scala
[warn] -- Warning: /Users/odersky/workspace/dotty/compiler/src/dotty/tools/dotc/reporting/messages.scala:2145:15
[warn] 2145 |        |    ${Magenta("case a @ C()")} => 2
[warn]      |               ^
[warn]      |Change in given search preference for dotty.tools.dotc.printing.Formatting.ShownDef.Show[Product | Null] between alternatives (dotty.tools.dotc.printing.Formatting.ShownDef.Show.given_Show_Product :
[warn]      |  dotty.tools.dotc.printing.Formatting.ShownDef.Show[Product]) and (dotty.tools.dotc.printing.Formatting.ShownDef.Show.oldShowNull :
[warn]      |  [X]
[warn]      |    (implicit evidence$1: dotty.tools.dotc.printing.Formatting.ShownDef.Show[X])
[warn]      |      : dotty.tools.dotc.printing.Formatting.ShownDef.Show.oldShowNull[X]
[warn]      |)
[warn]      |Previous choice          : the first alternative
[warn]      |New choice from Scala 3.6: the second alternative
```
I believe there is some funky thing going on with nulls. Probably unsafe nulls somewhere. Anyway what seems to happen is that before Product's given was selected for an argument of `Product | Null`, which seems to indicate that the two types were seen as equivalent. Anyway, if we compare
`Show[Product]` with `Show[X | Null]` it seems that Show[Product] won the type score and lost the owner score, so it was a draw. But then the second criterion kicked in which rules that alternatives without any implicit arguments take priority over alternatives with explicit
arguments. The given for Product was unconditional but the given for X | Null has a context bound. So Show[Product] won.

But once we switch to more general, we have that Show[Product] loses both type and owner score, so Show[X | Null] won. And that seems to have
caused the classcast exceptions. I am not sure why, the logic for Shown was too hard for me to follow. The fix is to move Show[X | Null] to have
lowest priority.
@odersky
Copy link
Contributor

odersky commented Jul 29, 2024

@WojciechMazur I see. Yes, probably it's best to temporarily revert #21257.

@WojciechMazur
Copy link
Contributor

This PR causes at least 1 regression in lemonlabsuk/scala-uri
More issues will follow in the future as soon as I would be able to extract them from other regression caused by changes merged recently to main.

import scala.language.implicitConversions

trait QueryKey[A]
object QueryKey extends QueryKeyInstances
sealed trait QueryKeyInstances:
  implicit val stringQueryKey: QueryKey[String] = ???

trait QueryValue[-A]
object QueryValue extends QueryValueInstances 
sealed trait QueryValueInstances1:
  implicit final val stringQueryValue: QueryValue[String] = ???
  implicit final val noneQueryValue: QueryValue[None.type] = ???

sealed trait QueryValueInstances extends QueryValueInstances1:
  implicit final def optionQueryValue[A: QueryValue]: QueryValue[Option[A]] = ???

trait QueryKeyValue[A]
object QueryKeyValue:
  implicit def tuple2QueryKeyValue[K: QueryKey, V: QueryValue]: QueryKeyValue[(K, V)] = ???


@main def Test = summon[QueryKeyValue[(String, None.type)]] // error

yields

-- [E172] Type Error: /Users/wmazur/projects/sandbox/src/main/scala/usage.scala:22:59 
22 |@main def Test = summon[QueryKeyValue[(String, None.type)]]
   |                                                           ^
   |No best given instance of type QueryKeyValue[(String, None.type)] was found for parameter x of method summon in object Predef.
   |I found:
   |
   |    QueryKeyValue.tuple2QueryKeyValue[String, None.type](QueryKey.stringQueryKey,
   |      QueryValue.optionQueryValue[A](
   |        /* ambiguous: both value stringQueryValue in trait QueryValueInstances1 and value noneQueryValue in trait QueryValueInstances1 match type QueryValue[A] */
   |          summon[QueryValue[A]]
   |      )
   |    )
   |
   |But both value stringQueryValue in trait QueryValueInstances1 and value noneQueryValue in trait QueryValueInstances1 match type QueryValue[A].

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

Successfully merging this pull request may close these issues.

Regression in implicit search in mixed implicit/givens sources
3 participants