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

Tweaks to given priority #21305

Closed
wants to merge 7 commits into from
Closed

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jul 31, 2024

  • Don't treat givens as higher priority than implicits. The previous logic was faulty anyway.
  • Drop special handling of NotGiven in prioritization. The previous logic pretended to do so, but was ineffective.
  • Don't use old priority in general for implicit/implicit pairs. This would make upgrading to givens a constant struggle. But do use it as a tie breaker in the case of ambiguities.
  • Also use owner score as a tie breaker if owner score and type score differ.

This PR is a consolidation of #21273, without the going back and forth.

 - Don't treat givens as higher priority than implicits. The previous logic was faulty anyway.
 - Drop special handling of NotGiven in prioritization. The previous logic pretended to do so,
   but was ineffective.
 - Don't use old priority in general for implicit/implicit pairs. This would make upgrading to givens a constant
   struggle. But do use it as a tie breaker in the case of ambiguities.
 - Also use owner score as a tie breaker if after all other tests we still have an ambiguity.
Previously warnings were produced but not shown since at the same position we already have an ambiguity
error. We now add the note to the error message.
@odersky
Copy link
Contributor Author

odersky commented Jul 31, 2024

This PR reverts the changes to PPrint that turned a given back into an implicits. These are no longer needed.

On the other hand, we do need a change to a scala-collection-compat test.

@odersky
Copy link
Contributor Author

odersky commented Jul 31, 2024

I also added a commit that will show priority changes under 3.6-migration as additions to error messages. Previously these were usually swallowed since a warning was suppressed by the ambiguity error at the same position.

Copy link
Contributor

@EugeneFlesselle EugeneFlesselle left a comment

Choose a reason for hiding this comment

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

The logic behind the changes seems well-grounded to me.
We still need to see to what the extend the openCB is remains impacted by the changes in given prioritization (cc @WojciechMazur).

On a minor note, we should update the internal doc to reflect everything as you summarized:

  • At compareAlternatives: use old priority in the case of ambiguities in new priority
  • At Applications#compare: use owner score as a tie breaker if after all other tests we still have an ambiguity
    • and update its' line still mentioning "A1's type is more specific than A2's type"

compiler/src/dotty/tools/dotc/typer/Applications.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/typer/Applications.scala Outdated Show resolved Hide resolved
result match
case result: SearchFailure =>
result.reason match
case ambi: AmbiguousImplicits => ambi.priorityChangeWarning = msg
Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch that these used to get swallowed up!
Couldn't it also be that there are multiple "critical" msgs found in result, in which case we way be overwriting only the last one into ambi.priorityChangeWarning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. We should make a List.

if result != 0 then result
else if strippedType1 eq fullType1 then
if strippedType2 eq fullType2
then drawOrOwner // no implicits either side: its' a draw
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do drawOrOwner here, as opposed to in compareWithTypes as you had in 39c49ad ?

We may also want to use the owner as the final tie breaker in the case where: we have an ambiguity with the strippedTypes, and that both alternatives have implicit parameters, and that the fullTypes are still ambiguous.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It turns out there are counter-examples for both schemes. If we do drawOrOwner here, StringFormatterTest does the right thing, but slick-migrations does not work anymore. If we do drawOrOwner in compareTypes, slick-migratons works but StringFormatterTest fails. The question is what do we prioritize case of a draw? The owner or cases without additional implicit parameters? I'll try the other scheme as well. At least StringFormatterTest has an easy fix.

Co-authored-by: Eugene Flesselle <eugene@flesselle.net>
@WojciechMazur
Copy link
Contributor

WojciechMazur commented Jul 31, 2024

Based on nafg/slick-migration-api

There's one more issue found in the OpenCB that's present in this PR but not in latest Nightly

trait Migration
object Migration:
  implicit class MigrationConcat[M <: Migration](m: M):
    def &[N <: Migration, O](n: N)(implicit ccm: CanConcatMigrations[M, N, O]): O = ???

trait ReversibleMigration extends Migration
trait MigrationSeq extends Migration
trait ReversibleMigrationSeq extends MigrationSeq with ReversibleMigration

trait ToReversible[-A <: Migration]
object ToReversible:
  implicit val reversible: ToReversible[ReversibleMigration] = ???
class CanConcatMigrations[-A, -B, +C]
trait CanConcatMigrationsLow:
  implicit def default[A <: Migration, B <: Migration]: CanConcatMigrations[A, B, MigrationSeq] = ???
object CanConcatMigrations extends CanConcatMigrationsLow:
  implicit def reversible[A <: Migration, B <: Migration](implicit reverseA: ToReversible[A],
                                                          reverseB: ToReversible[B]): CanConcatMigrations[A, B, ReversibleMigrationSeq] = ???

@main def Test = 
  val rm: ReversibleMigration = ???
  val rms = rm & rm & rm
  summon[rms.type <:< ReversibleMigrationSeq] // error Cannot prove that (rms : slick.migration.api.MigrationSeq) <:< slick.migration.api.ReversibleMigrationSeq.

@EugeneFlesselle
Copy link
Contributor

There's one more issue found in the OpenCB that's present in this PR but not in latest Nightly

I'll try to minimize it @\odersky

@WojciechMazur
Copy link
Contributor

WojciechMazur commented Jul 31, 2024

In summary there seems to be 4 failures found in OpenCB that are not present in Nightly. I'll try to provide reproducers for these

Project Version Build URL Notes
nafg/slick-migration-api 0.10.0 Open CB logs Reproducer above
scala/scala-collection-compat 2.12.0 Open CB logs
scala/scala-collection-contrib 0.3.0 Open CB logs Fails on main (latest nightly) with different error
com-lihaoyi/upickle 3.3.1 -> 4.0.0 Open CB logs Beside compilation error there is also a regression in implicit search, reaches limit of tried elements (200k elements) when deriving recursive 6 levels deep generic Json codecs

@EugeneFlesselle
Copy link
Contributor

The issue based on nafg/slick-migration-api comes down to:

trait Migration
trait Migration2 extends Migration


class Cov[+C]

object Cov:
  implicit def default: Cov[Migration] = ???
  implicit def reversible: Cov[Migration2] = ???


def foo[O](using Cov[O]): O = ???

@main def Test =
  val f1 = foo
  val f2: Migration2 = f1 // error

It used to pick reversible (the most specific), but now picks default (the most general).

Modifying the last line of the reproducer to summon[rms.type <:< MigrationSeq] compiles as expected.

@WojciechMazur
Copy link
Contributor

nafg/slick-migration-api actually dependent on this behaviour for it's runtime as the comment suggests
https://github.com/nafg/slick-migration-api/blob/510c94f4ec084ed0f05e1d7adc100610328bcdd7/src/main/scala/slick/migration/api/Migration.scala#L16-L23
However, probably this is probably not a think that can be achieved anymore. Project currently cross-compiles with Scala 2, so that's why this implicit-based technique is used. Scala 3 alternative would be to use a dedicated match type probably

…ents

This makes slick-migration-api-example work and makes the original scala-uri.scala fail.
See the comment in neg/scala-uri.scala for why this is so. It also needs a change in the
givens in dotty.tools.reporting.Formatting.

The motivation for honoring owner score over the others is that it is often used for explicit
prioritization, so we should take it into account more than other secondary criteria.
@odersky
Copy link
Contributor Author

odersky commented Jul 31, 2024

OK. So the last commit reverts to the previous disambiguation by owner before considering other criteria such as whether an alternative is conditional or unconditional. This fixes nafg/slick-migration-api but breaks some other things. The motivation for this change is that people often use class hierarchies for explicit prioritization, so we want to honor this as much as we can.

@odersky
Copy link
Contributor Author

odersky commented Jul 31, 2024

About the 4 reproducers: slick-migration-api should be fixed by latest commit, scala-collection-compat is a known case, and fixed in small CB. The others need more investigation. Not sure whether the latest commit would fix them, too.

@EugeneFlesselle
Copy link
Contributor

About the 4 reproducers: slick-migration-api should be fixed by latest commit, scala-collection-compat is a known case, and fixed in small CB. The others need more investigation.

scala/scala-collection-contrib seems to be depending on scala.collection.BuildFrom, so it might be the same issue as scala-collection-compat

@WojciechMazur
Copy link
Contributor

I've tried to test this PR on top of 3.5.0-RC6 to test potential RC7, there seems to be one unresolved issue that poped up in the previous PR #21273 (comment) related to lemonlabsuk/scala-uri

@odersky
Copy link
Contributor Author

odersky commented Aug 2, 2024

Yes, the scala-uti problem is known (and fixable on their side). I mention it in the last commit message:

Disambiguate by owner score before considering further implicit argum…
…ents

This makes slick-migration-api-example work and makes the original scala-uri.scala fail.
See the comment in neg/scala-uri.scala for why this is so. It also needs a change in the
givens in dotty.tools.reporting.Formatting.

The motivation for honoring owner score over the others is that it is often used for explicit
prioritization, so we should take it into account more than other secondary criteria.

@Gedochao Gedochao linked an issue Aug 2, 2024 that may be closed by this pull request
@WojciechMazur
Copy link
Contributor

WojciechMazur commented Aug 2, 2024

To my surprise this change is now leading to less ambiguous implicit, but in general there is ~1k more warnings in more projects, based on ~1570 projects

Given stats for 3.5.0-RC6
Given alternatives choice: Some(the second alternative) - 483
Given alternatives choice: Some(none - it's ambiguous) - 762
Given alternatives choice: Some(the first alternative) - 264
Projects with changed alternatives warnings - 23

Given stats for 3.5.0-RC6-bin-ef43053-SNAPSHOT
Given alternatives choice: Some(the second alternative) - 1868
Given alternatives choice: Some(none - it's ambiguous) - 21
Given alternatives choice: Some(the first alternative) - 831
Projects with changed alternatives warnings - 88

I wonder if that's something we should investigate?

Affected projects:
Choice warnings in 3.5.0-RC6:

typelevel/spotted-leopards - 1
daghemberg/problemutils - 1
h8io/borscht - 1
gemini-hlsw/lucuma-itc - 1
dapperware/dappermongo - 1
lampepfl/gears - 2
higherkindness/droste - 2
rlemaitre/pillars - 2
gemini-hlsw/lucuma-schemas - 3
giiita/refuel - 4
hnaderi/edomata - 4
makiftutuncu/as - 5
kitlangton/neotype - 8
sageserpent-open/kineticmerge - 18
typelevel/shapeless-3 - 20
rssh/dotty-cps-async - 21
gemini-hlsw/lucuma-core - 25
gvolpe/trading - 29
kevin-lee/refined4s - 47
tomasmikula/libretto - 79
nationalarchives/dr2-preservica-client - 160
kevin-lee/effectie - 344
takapi327/ldbc - 731 

Choice warnings in 3.5.0-RC6-bin-ef43053-SNAPSHOT:

scala-tsi/scala-tsi - 1
typelevel/spotted-leopards - 1
dapperware/dappermongo - 1
typelevel/spire - 1
h8io/borscht - 1
zio/zio-direct - 1
geirolz/erules - 1
gemini-hlsw/refined-algebra - 1
mkroli/dns4s - 1
daghemberg/problemutils - 1
scanamo/scanamo - 1
ant8e/uuid4cats-effect - 1
gzoller/scalajack - 1
eed3si9n/scalaxb - 1
jap-company/fields - 1
tpolecat/doobie - 1
dylemma/xml-spac - 1
scala/scala-collection-compat - 2
softwaremill/tapir - 2
pityka/saddle - 2
gemini-hlsw/lucuma-catalog - 2
lampepfl/gears - 2
akka/alpakka - 2
rlemaitre/pillars - 2
ajozwik/protoquill-generic - 2
pomadchin/tagless-mid - 2
massimosiani/monix-newtypes-cats - 2
xuwei-k/zeroapply - 2
andimiller/hedgehogs - 2
gemini-hlsw/giapi-scala - 2
scala/scala-collection-contrib - 2
darrenjw/scala-glm - 2
devsisters/shardcake - 2
msgpack4z/msgpack4z-core - 3
zio/zio-connect - 3
gaelrenoux/tranzactio - 3
crobox/clickhouse-scala-client - 4
higherkindness/droste - 4
hnaderi/edomata - 4
malliina/mobile-push - 4
giiita/refuel - 4
makiftutuncu/as - 5
scalikejdbc/scalikejdbc - 5
gemini-hlsw/lucuma-schemas - 5
ghostdogpr/caliban - 5
scalawilliam/xs4s - 5
marcinzh/turbolift - 5
reactivecore/rc-circe-json-schema - 5
geirolz/cats-xml - 6
http4s/http4s-armeria - 6
thoughtworksinc/dsl.scala - 6
joan38/kubernetes-client - 6
atnos-org/eff - 6
ingarabr/gcs-lock - 7
kitlangton/neotype - 8
theiterators/kebs - 10
laserdisc-io/tamer - 10
ovotech/natchez-extras - 11
gemini-hlsw/lucuma-itc - 13
zainab-ali/aquascape - 17
rssh/dotty-cps-async - 17
avokka/avokka - 18
sageserpent-open/kineticmerge - 20
scalanlp/breeze - 20
typelevel/shapeless-3 - 21
monix/monix - 23
scalalandio/chimney - 23
zio/zio-protoquill - 25
sagifogel/proptics - 31
zio/zio-prelude - 31
gvolpe/trading - 34
kaizen-solutions/trace4cats-zio-extras - 42
hagay3/skuber - 45
lancewalton/treelog - 47
kevin-lee/refined4s - 47
lemonlabsuk/scala-uri - 51
kailuowang/mau - 51
gemini-hlsw/lucuma-core - 53
lichess-org/lila - 55
tminglei/slick-pg - 69
zio/interop-cats - 74
typelevel/cats - 78
tomasmikula/libretto - 79
fthomas/refined - 96
nationalarchives/dr2-preservica-client - 160
scalaprops/scalaprops - 171
kevin-lee/effectie - 389
takapi327/ldbc - 731

@odersky
Copy link
Contributor Author

odersky commented Aug 2, 2024

Great to have data! I have a theory which might explain it, but we need to do further investigations to be sure.

We now have two new disambiguation measures:

  • Prefer owner score over type score if they conflict.
  • If there's with the new rules an ambiguity for old style implicit/implicit pairs, resolve with the old priorities

Previously we always resolved implicit/implicit pairs with the old rules. But that would give a constant risk for priority changes when these implicits are rewritten to be givens.

So this means:

  • With owner score disambiguation, we get way fewer ambiguities, which is good.
  • But with fewer ambiguities we also get fewer occasions where we would resolve with the old rules for implicit/implicit pairs.

I can do another experimental tweak that disables owner score disambiguation for implicit/implicit pairs so that then the old rules would kick in. Let's see how that performs.

@odersky
Copy link
Contributor Author

odersky commented Aug 3, 2024

The last commit passes the tests, but I think we need to back out of this line of thought. The problem is that we can do only one of two mutually exclusive things:

  • handle implicit/implicit pairs different from other pairs
  • make implicit have the same priority as given

If we do both, the compareAlternatives relation will no longer be a partial order since transitivity will fail. An example, which holds for the previous few commits:

  • We have two givens G1 and G2 and two implicits I1 and I2. G1 <: I1 and I2 <: G2 according to the "general is better" ordering. I1 and I2 are ambiguous, and so are G1 and G2. We fix the I1/I2 ambiguity by resolving I1 and I2 according to the old rules, i.e. "specific is better". Now we have a chain G1 <: I1 <: I2 <: G2. Transitivity would imply G1 <: G2 but this is false. Or, worse we could have G2 <: G1 initially, so now we have two possible outcomes for the G1/G2 test depending on whether we consult I1/I2 in the middle or not.

Without transitivity, the priority computation for implicits becomes dependent on the order in which we compare different implicits. That we cannot have.

I'd like to try a variant of this PR which does the new owner score disambiguation but keeps the "given beats implicit" rule. The original code in 3.4 should have prioritized givens over implicits but due to a one character typo failed to do so.
😢

@EugeneFlesselle
Copy link
Contributor

The last commit passes the tests, but I think we need to back out of this line of thought. The problem is that we can do only one of two mutually exclusive things:

  • handle implicit/implicit pairs different from other pairs
  • make implicit have the same priority as given

If we do both, the compareAlternatives relation will no longer be a partial order since transitivity will fail.

I think we could address the transitivity issue, similarly to what we had discussed previously, by making tryImplicit first compare between all the implicits, and then between all the givens independently.

Nevertheless, FWIW I think the second option alone (i.e. as in 3d9ce8e) would be best. Even if the first option (combined with the new owner score disambiguation) may give fewer changes in resolution, we would only be delaying those warnings to when old-style implicits are migrated to new-style givens. In addition, having different rules for implicits and givens would likely make predicting and/or debugging implicit resolution in large projects much trickier.

In any case we can use the last commit 62acaf1 to validate the hypothesis of #21305 (comment).

@odersky
Copy link
Contributor Author

odersky commented Aug 3, 2024

But the second option alone is supposed to be even worse than this:

Given stats for 3.5.0-RC6-bin-ef43053-SNAPSHOT
Given alternatives choice: Some(the second alternative) - 1868
Given alternatives choice: Some(none - it's ambiguous) - 21
Given alternatives choice: Some(the first alternative) - 831
Projects with changed alternatives warnings - 88

I am not sure this degree of breakage is acceptable. The alternative, ie. treating implicits and givens separately has two shortcomings:

  • We need to be careful when we mix givens and implicits. Probably not so much an issue since implicits that compete for a type tend to be defined together.
  • Rewriting implicits to givens will be harder because it contains a constant risk of different selections. So we might have to keep implicits around for longer. But since we still have ownerScore as a disambiguator it should be possible to migrate an implicits hierarchy, keeping explicit priorities by owner.

I think the main difference is between library authors and library users. Library authors will have to be more careful if implicitis and givens are treated differently. But with work they can keep the old priorities. Library users will be safer since the many libraries that currently use implicits continue to be usable in the same way.

All of these needs to be backed up by running OpenCB on the alternative and comparing with the results here.

@EugeneFlesselle
Copy link
Contributor

I am not sure this degree of breakage is acceptable.

Right indeed. We should also keep as an option making mostGeneral resolution a scala.language feature import. That would be the simplistic alternative, but we should make sure the potential added complexity of a prioritization scheme with reasonable breakages is worth avoiding putting the change under an import, before committing to it.

@odersky
Copy link
Contributor Author

odersky commented Aug 6, 2024

Superseded by #21328

@odersky odersky closed this Aug 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants