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

Backport "Fix healAmbiguous to compareAlternatives with disambiguate = true" to 3.5.0 #21344

Merged

Conversation

WojciechMazur
Copy link
Contributor

Backports #21339 to Scala 3.5.0-RC7

EugeneFlesselle and others added 8 commits August 7, 2024 12:41
Before the changes, if
`isAsGoodValueType` was called with an extension and a given conversion,
it would prefer the conversion over the extension,
because only the former yielded true in `isGiven`.

Which contradicted the logic from searchImplicit which
preferred extension over conversions for member selection.
We now use a left-biased scheme, as follows.

From 3.6 on:

 - A given x: X is better than a given or implicit y: Y if y can be instantiated/widened to X.
 - An implicit x: X is better than a given or implicit y: Y if y can be instantiated to a supertype of X.
 - Use owner score for givens as a tie breaker if after all other tests we still have an ambiguity.

This is not transitive, so we need a separate scheme to work around that.

Other change:

 - Drop special handling of NotGiven in prioritization. The previous logic pretended to do so,
   but was ineffective.
We only have transitivity between givens or between implicits. To cope with that

 - We tank first all implicits, giving a best implicit search result.
 - Then we rank all givens startign with the implicit result. If there is
   a given that is better than the best implicit, the best given will be chosen.
   Otherwise we will stick with the best implicit.
Warnings from 3.6, change in 3.7. This is one version later than
originally planned.
Make the wording of a priority change warning message stable under different orders of eligibles.
We now always report the previously chosen alternative first and the new one second.

Note: We can still get ambiguities by fallging different pairs of alternatives depending on initial order.
On the final result, compared with all the ambiguous candidates we are trying
to recover from. We should still use `disambiguate = false` when filtering the
`pending` candidates for the purpose of warnings, as in the other cases.

Before the changes, it was possible for an ambiguous SearchFailure
to be healed by a candidate which was considered better (possibly only) under
a prioritization scheme different from the current one.

As an optimization, we can avoid redoing compareAlternatives in versions which
could have only used the new prioritization scheme to begin with.

Also restores behaviour avoiding false positive warnings.
Specifically, in cases where we could report a change in prioritization,
despite having not yet done `tryImplicit` on the alternative,
i.e. it was only compared as part of an early filtering

See scala#21045 for related changes
@@ -0,0 +1,31 @@
//> using options -source:3.6
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Backported version required -source:3.6 which is not needed in main

Copy link
Contributor

Choose a reason for hiding this comment

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

main has the baseVersion set to 3.6 already, so we get the CompareScheme.New by default.
While in 3.5 we maintain the intermediate comparison scheme, in which the test case used to be un-ambiguous, so I think this is as expected.

@WojciechMazur
Copy link
Contributor Author

WojciechMazur commented Aug 7, 2024

This PR couses the same regressions as reported in #21328 (comment)

Project Version Build URL Notes
neandertech/langoustine 0.0.22 Open CB logs missing implicit (reproduction below)
softwaremill/tapir 1.11.0 Open CB logs crash
tpolecat/doobie 1.0.0-RC5 Open CB logs missing implicit
virtuslab/scala-cli 1.4.1 Open CB logs crash

@EugeneFlesselle
Copy link
Contributor

This PR couses the same regressions as reported in #21328 (comment)

But only in 3.6 now right ?? the only major difference with that PR is that we delayed the changes by one version

@WojciechMazur
Copy link
Contributor Author

It's happening in default, that is -source:3.5 compilation. I'm working on reproducers right now for missing implicits

@EugeneFlesselle
Copy link
Contributor

It's happening in default, that is -source:3.5 compilation. I'm working on reproducers right now for missing implicits

The only place emitting warnings with messages of the form "Given search preference for ... between alternatives ... none - it's ambiguous" is in compareAlternatives under the condition that isWarnPriorityChangeVersion which is equal to:

val sv = Feature.sourceVersion
val isLastOldVersion = sv.stable == SourceVersion.`3.6`
val isWarnPriorityChangeVersion = isLastOldVersion || sv == SourceVersion.`3.7-migration`

So I am very confused about how we can still be getting these warnings where the source version is 3.5.

@WojciechMazur
Copy link
Contributor Author

Reproducer for neandertech/langoustine

object serializer:
  trait Reader[T]
  trait Writer[T]
  // Needs to be implicit val
  implicit val UnitReader: Reader[Unit] = ???
  implicit val StringReader: Reader[String] = ???
  // A way to derive instances needs to be available
  inline given superTypeReader[T: scala.reflect.ClassTag]: Reader[T] = ???
import serializer.Reader

trait Codec[T]
trait Channel[F[_]]:
  def notificationStub[In: Codec](): In => F[Unit]
trait Monadic[F[_]]

sealed abstract class LSPNotification():
  type In
  given inputReader: Reader[In]

class PreparedNotification[X <: LSPNotification](val x: X, val in: x.In):
  type In = x.In

trait Communicate[F[_]]:
  def notification[X <: LSPNotification](notif: X, in: notif.In): F[Unit]

object Communicate:
  given codec[T: Reader]: Codec[T] = ???

  def channel[F[_]: Monadic](channel: Channel[F]) =
    new Communicate[F]:
      override def notification[X <: LSPNotification](notif: X, in: notif.In): F[Unit] = 
        channel.notificationStub().apply(in) // error, does not infer notif.In type so cannot summon correct implicit

@WojciechMazur
Copy link
Contributor Author

So I am very confused about how we can still be getting these warnings where the source version is 3.5.

The regressions are not about warnings. These are hard errors the first reproducer fails even with -source:3.4

@EugeneFlesselle
Copy link
Contributor

EugeneFlesselle commented Aug 7, 2024

The regressions are not about warnings. These are hard errors the first reproducer fails even with -source:3.4

I see, thank you for the reproducer

This is caused by 8a41389, that commit is necessary only to maintain transitivity in the new comparison scheme. I will investigate what exactly is happing there. In the mean time, it should be fine to drop that commit for the purpose of the backport. We should at least relaunch the open CB on this PR - 8a41389 to verify there are no other issues.

@EugeneFlesselle
Copy link
Contributor

EugeneFlesselle commented Aug 7, 2024

Reproducer for neandertech/langoustine

Minimized a little:

trait Reader[T]

// Needs to be implicit val
implicit val UnitReader: Reader[Unit] = ???
implicit val StringReader: Reader[String] = ???
// A way to derive instances needs to be available
given superTypeReader[T: scala.reflect.ClassTag]: Reader[T] = ???

object Notif:
  type In
  given inputReader: Reader[In] = ???

object Test:

  def notificationStub[T: Reader]: T => Unit = ???

  val in: Notif.In = ???
  notificationStub.apply(in) // error since 8a41389d

This is caused by 8a41389, that commit is necessary only to maintain transitivity in the new comparison scheme. I will investigate what exactly is happing there.

In 8a41389, we did not account for the fact that the result of rank also depends on the rfailures parameter. Specifically, once there are no more pending candidates, we do:

case nil =>
  if (rfailures.isEmpty) found
  else found.recoverWith(_ => rfailures.reverse.maxBy(_.tree.treeSize))

Having split the ranking of implicits and givens changes the accumulated rfailures with which rank finishes, and thereby the returned SearchFailure. Which in turn seems to prevent from even considering the Notif.inputReader candidate.

@WojciechMazur
Copy link
Contributor Author

Reproducer for tpolcat/doobie

trait Text[T]
trait Read[A]
object Read extends ReadImplicits:
  implicit val unit: Read[Unit] = ???
trait ReadImplicits:
  import scala.deriving.*
  given roe: Read[Option[EmptyTuple]] = ???
  given rou: Read[Option[Unit]] = ???
  given cons1[H, T <: Tuple](using Read[Option[H]], Read[Option[T]]): Read[Option[H *: T]] = ???

trait Fragment:
  def query[B: Read]: String = ???

@main def Test = 
  val f: Fragment = ???
   f.query // error

This snipet was compiled previously only thanks to a big amount of luck. Typical usage of query method requires to explicitly provide a type argument. Previously thanks to having implicit val unit: Read[Unit] as the only alternative in object Read this instance was chosen, which later infered whole expression to query[Unit]. I'm not sure if the library author expected that (probably not as I can see multiple usaged of query[Unit] in the codebase).
Now it fails with:

-- [E172] Type Error: /Users/wmazur/projects/sandbox/src/test.scala:16:10 ------
16 |   f.query // error
   |          ^
   |No best given instance of type Read[B] was found for a context parameter of method query in trait Fragment.
   |I found:
   |
   |    Read.cons1[H, T](
   |      /* ambiguous: both given instance roe in trait ReadImplicits and given instance rou in trait ReadImplicits match type Read[Option[H]] */
   |        summon[Read[Option[H]]],
   |    Read.roe)
   |
   |But both given instance roe in trait ReadImplicits and given instance rou in trait ReadImplicits match type Read[Option[H]].
1 error found

@WojciechMazur
Copy link
Contributor Author

All 4 regressions are gone after reverting 8a41389, it does not cause any new problems.

@odersky
Copy link
Contributor

odersky commented Aug 8, 2024

All 4 regressions are gone after reverting 8a41389, it does not cause any new problems.

OK great! I was thinking about how this could cause problems. The only thing that should be different is now we typecheck more implicits. Say we have a simple given and a complicated recursive implicit. Before 8a41389, we would typecheck the given first and then the implicit. Afterwards, it's the reverse. This could very well cause problems. One way to cause fewer problems is to start with the simplest term after sorting, then compare it with all terms of the same implicit-vs-given-class, then do the rest. But in general it seems impossible to guarantee no regressions when we change the order in which candidates are type checked.

@WojciechMazur WojciechMazur merged commit 95caecb into scala:release-3.5.0 Aug 8, 2024
24 checks passed
@WojciechMazur WojciechMazur deleted the backport-3.5.0/21339 branch August 8, 2024 08:09
EugeneFlesselle added a commit to dotty-staging/dotty that referenced this pull request Aug 9, 2024
EugeneFlesselle added a commit to dotty-staging/dotty that referenced this pull request Aug 9, 2024
* `neandertech/langoustine` - [scala#21344 (comment)]
(scala#21344 (comment))
* `tpolcat/doobie` - [scala#21344 (comment)]
(scala#21344 (comment))

Fixes scala#21352
EugeneFlesselle added a commit that referenced this pull request Aug 9, 2024
This reverts commit 7c4bd67
See [#21344 comment]
(#21344 (comment))

We will have to reconsider how to alleviate the transitory problem
before releasing 3.6.

Fixes #21320
Fixes #21352
WojciechMazur added a commit that referenced this pull request Aug 13, 2024
…= true" to 3.5.1 (#21372)

Backports #21344 to Scala 3.5.1-RC2 using 3.5.0-RC7 backport #21344
WojciechMazur added a commit that referenced this pull request Aug 28, 2024
This reverts commit 7c4bd67
See #21344 (comment)

Fixes #21320

[Cherry-picked 58f3407][modified]
WojciechMazur pushed a commit that referenced this pull request Aug 28, 2024
* `neandertech/langoustine` - [#21344 (comment)]
(#21344 (comment))
* `tpolcat/doobie` - [#21344 (comment)]
(#21344 (comment))

Fixes #21352

[Cherry-picked 5408a80]
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.

3 participants