-
Notifications
You must be signed in to change notification settings - Fork 21
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
Implicit argument list modifier affects overload resolution with explicit arguments #10526
Comments
I think that the logic is there in an attempt to bring the behaviour with implicit arguments into line with the behaviour with default arguments. |
Note that this will be likely affected by the future requirement to pass implicit arguments using explicitly. I.e. in the future you have to write:
I believe that doing this change will pave the way to a natural fix: If |
This is an attempt to make implicit selection rules align more closely with people's intuitions and the spec's aspirations that it be consistent with normal overload resolution rules. It also fixes scala/bug#10526. Candidate implicits are ordered via Infer#isStrictlyMoreSpecific which delegates to Infer#isAsSpecific. Prior to this commit the latter treats all methods with an implicit argument list as if they were nullary methods, leading to scala/bug#10526 ... I believe that the motivation for this behaviour was to allow methods which are intended to appear to users to be nullary to have implicit arguments without affecting their overload resolution behaviour wrt same-named methods which are actually nullary. Whilst this does the right thing in some circumstances, it leads to oddities such as scala/bug#10526 and also means that the specificity test used to order and select implicits is very crude, in effect only considering the result type of an implicit method, not its implicit arguments. This commit addresses these issues by making the "treat implicit methods as nullary" logic explicitly flagged and enabling it only at the relevant call site in Inferencer#inferExprAlternative. Normal overloading rules are unchanged, and the implicit selection logic is extracted out to ImplicitSearch#Improves. There the relative ordering of a pair of candidate implicits is computed as follows, 1. The candidates are compared by their result type, the most specific by normal overload rules being selected (this is consistent with the behaviour prior to this commit). 2. If (1) is a tie then we compare the lengths of the implicit argument lists of the candidates (if any). The candidate with the longest argument list wins (this matches users intuitions that methods with more implicit arguments are "more demanding" hence more specific). 3. If (2) is a tie then we have a pair of candidates with implicit argument lists of equal length and which are both equally specific wrt their result type/the expected type. We now apply the full normal overloading resolution rules and choose the most specific. This change makes combining implicits from multiple implicit scopes a great deal more straightforward, since we can now rely on more than just the result type of the implicit definition to guide implicit selection. With this commit the following works as expected, class Show[T](val i: Int) object Show { def apply[T](implicit st: Show[T]): Int = st.i implicit val showInt: Show[Int] = new Show[Int](0) implicit def fallback[T]: Show[T] = new Show[T](1) } class Generic object Generic { implicit val gen: Generic = new Generic implicit def showGen[T](implicit gen: Generic): Show[T] = new Show[T](2) } object Test extends App { assert(Show[Int] == 0) assert(Show[String] == 1) assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list } Thanks to the use of normal overload resolution rules we also have the ability to use a "phantom" implicit parameter with arguments ordered by subtyping to explicitly prioritize implicit definitions, something for which language extensions of one form or another have occasionally been proposed, class Low object Low { implicit val low: Low = new Low } class Medium extends Low object Medium { implicit val medium: Medium = new Medium } class High extends Medium object High { implicit val high: High = new High } class Foo[T](val i: Int) object Foo { def apply[T](implicit fooT: Foo[T]): Int = fooT.i implicit def foo[T](implicit priority: Low): Foo[T] = new Foo[T](0) implicit def foobar[T](implicit priority: Low): Foo[Bar[T]] = new Foo[Bar[T]](1) implicit def foobarbaz(implicit priority: Low): Foo[Bar[Baz]] = new Foo[Bar[Baz]](2) } class Bar[T] object Bar { implicit def foobar[T](implicit priority: Medium): Foo[Bar[T]] = new Foo[Bar[T]](3) implicit def foobarbaz(implicit priority: Medium): Foo[Bar[Baz]] = new Foo[Bar[Baz]](4) } class Baz object Baz { implicit def baz(implicit priority: High): Foo[Bar[Baz]] = new Foo[Bar[Baz]](5) } object Test extends App { assert(Foo[Int] == 0) assert(Foo[Bar[Int]] == 3) assert(Foo[Bar[Baz]] == 5) } This commit changes the outcome of only one partest (test/files/neg/t7289_status_quo.scala) which is intended to exactly capture the current failure behaviour in a specific implicit resolution scenario ... I believe the new (successful) behaviour is an improvement on the old.
This is an attempt to make implicit selection rules align more closely with people's intuitions and the spec's aspirations that it be consistent with normal overload resolution rules. It also fixes scala/bug#10526. Candidate implicits are ordered via Infer#isStrictlyMoreSpecific which delegates to Infer#isAsSpecific. Prior to this commit the latter treats all methods with an implicit argument list as if they were nullary methods, leading to scala/bug#10526 ... I believe that the motivation for this behaviour was to allow methods which are intended to appear to users to be nullary to have implicit arguments without affecting their overload resolution behaviour wrt same-named methods which are actually nullary. Whilst this does the right thing in some circumstances, it leads to oddities such as scala/bug#10526 and also means that the specificity test used to order and select implicits is very crude, in effect only considering the result type of an implicit method, not its implicit arguments. This commit addresses these issues by making the "treat implicit methods as nullary" logic explicitly flagged and enabling it only at the relevant call site in Inferencer#inferExprAlternative. Normal overloading rules are unchanged, and the implicit selection logic is extracted out to ImplicitSearch#Improves. There the relative ordering of a pair of candidate implicits is computed as follows, 1. The candidates are compared by their result type, the most specific by normal overload rules being selected (this is consistent with the behaviour prior to this commit). 2. If (1) is a tie then we compare the lengths of the implicit argument lists of the candidates (if any). The candidate with the longest argument list wins (this matches users intuitions that methods with more implicit arguments are "more demanding" hence more specific). 3. If (2) is a tie then we have a pair of candidates with implicit argument lists of equal length and which are both equally specific wrt their result type/the expected type. We now apply the full normal overloading resolution rules and choose the most specific. This change makes combining implicits from multiple implicit scopes a great deal more straightforward, since we can now rely on more than just the result type of the implicit definition to guide implicit selection. With this commit the following works as expected, class Show[T](val i: Int) object Show { def apply[T](implicit st: Show[T]): Int = st.i implicit val showInt: Show[Int] = new Show[Int](0) implicit def fallback[T]: Show[T] = new Show[T](1) } class Generic object Generic { implicit val gen: Generic = new Generic implicit def showGen[T](implicit gen: Generic): Show[T] = new Show[T](2) } object Test extends App { assert(Show[Int] == 0) assert(Show[String] == 1) assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list } Thanks to the use of normal overload resolution rules we also have the ability to use a "phantom" implicit parameter with arguments ordered by subtyping to explicitly prioritize implicit definitions, something for which language extensions of one form or another have occasionally been proposed, class Low object Low { implicit val low: Low = new Low } class Medium extends Low object Medium { implicit val medium: Medium = new Medium } class High extends Medium object High { implicit val high: High = new High } class Foo[T](val i: Int) object Foo { def apply[T](implicit fooT: Foo[T]): Int = fooT.i implicit def foo[T](implicit priority: Low): Foo[T] = new Foo[T](0) implicit def foobar[T](implicit priority: Low): Foo[Bar[T]] = new Foo[Bar[T]](1) implicit def foobarbaz(implicit priority: Low): Foo[Bar[Baz]] = new Foo[Bar[Baz]](2) } class Bar[T] object Bar { implicit def foobar[T](implicit priority: Medium): Foo[Bar[T]] = new Foo[Bar[T]](3) implicit def foobarbaz(implicit priority: Medium): Foo[Bar[Baz]] = new Foo[Bar[Baz]](4) } class Baz object Baz { implicit def baz(implicit priority: High): Foo[Bar[Baz]] = new Foo[Bar[Baz]](5) } object Test extends App { assert(Foo[Int] == 0) assert(Foo[Bar[Int]] == 3) assert(Foo[Bar[Baz]] == 5) } This commit changes the outcome of only one partest (test/files/neg/t7289_status_quo.scala) which is intended to exactly capture the current failure behaviour in a specific implicit resolution scenario ... I believe the new (successful) behaviour is an improvement on the old.
This is an attempt to make implicit selection rules align more closely with people's intuitions and the spec's aspirations that it be consistent with normal overload resolution rules. It also fixes scala/bug#10526. Candidate implicits are ordered via Infer#isStrictlyMoreSpecific which delegates to Infer#isAsSpecific. Prior to this commit the latter treats all methods with an implicit argument list as if they were nullary methods, leading to scala/bug#10526 ... I believe that the motivation for this behaviour was to allow methods which are intended to appear to users to be nullary to have implicit arguments without affecting their overload resolution behaviour wrt same-named methods which are actually nullary. Whilst this does the right thing in some circumstances, it leads to oddities such as scala/bug#10526 and also means that the specificity test used to order and select implicits is very crude, in effect only considering the result type of an implicit method, not its implicit arguments. This commit addresses these issues by making the "treat implicit methods as nullary" logic explicitly flagged and enabling it only at the relevant call site in Inferencer#inferExprAlternative. Normal overloading rules are unchanged, and the implicit selection logic is extracted out to ImplicitSearch#Improves. There the relative ordering of a pair of candidate implicits is computed as follows, 1. The candidates are compared by their result type, the most specific by normal overload rules being selected (this is consistent with the behaviour prior to this commit). 2. If (1) is a tie then we compare the lengths of the implicit argument lists of the candidates (if any). The candidate with the longest argument list wins (this matches users intuitions that methods with more implicit arguments are "more demanding" hence more specific). 3. If (2) is a tie then we have a pair of candidates with implicit argument lists of equal length and which are both equally specific wrt their result type/the expected type. We now apply the full normal overloading resolution rules and choose the most specific. This change makes combining implicits from multiple implicit scopes a great deal more straightforward, since we can now rely on more than just the result type of the implicit definition to guide implicit selection. With this commit the following works as expected, class Show[T](val i: Int) object Show { def apply[T](implicit st: Show[T]): Int = st.i implicit val showInt: Show[Int] = new Show[Int](0) implicit def fallback[T]: Show[T] = new Show[T](1) } class Generic object Generic { implicit val gen: Generic = new Generic implicit def showGen[T](implicit gen: Generic): Show[T] = new Show[T](2) } object Test extends App { assert(Show[Int] == 0) assert(Show[String] == 1) assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list } Thanks to the use of normal overload resolution rules we also have the ability to use a "phantom" implicit parameter with arguments ordered by subtyping to explicitly prioritize implicit definitions, something for which language extensions of one form or another have occasionally been proposed, class Low object Low { implicit val low: Low = new Low } class Medium extends Low object Medium { implicit val medium: Medium = new Medium } class High extends Medium object High { implicit val high: High = new High } class Foo[T](val i: Int) object Foo { def apply[T](implicit fooT: Foo[T]): Int = fooT.i implicit def foo[T](implicit priority: Low): Foo[T] = new Foo[T](0) implicit def foobar[T](implicit priority: Low): Foo[Bar[T]] = new Foo[Bar[T]](1) implicit def foobarbaz(implicit priority: Low): Foo[Bar[Baz]] = new Foo[Bar[Baz]](2) } class Bar[T] object Bar { implicit def foobar[T](implicit priority: Medium): Foo[Bar[T]] = new Foo[Bar[T]](3) implicit def foobarbaz(implicit priority: Medium): Foo[Bar[Baz]] = new Foo[Bar[Baz]](4) } class Baz object Baz { implicit def baz(implicit priority: High): Foo[Bar[Baz]] = new Foo[Bar[Baz]](5) } object Test extends App { assert(Foo[Int] == 0) assert(Foo[Bar[Int]] == 3) assert(Foo[Bar[Baz]] == 5) } This commit changes the outcome of only one partest (test/files/neg/t7289_status_quo.scala) which is intended to exactly capture the current failure behaviour in a specific implicit resolution scenario ... I believe the new (successful) behaviour is an improvement on the old.
|
Whoops! The issue is fixed in Dotty, not here: scala/scala3#3190. |
The following fails with an ambiguous overload,
However, remove the
implicit
argument list modifier and it compiles as expected,This is because the logic in
Inferencer#isAsSpecific
only compares the result types of method types with an implicit argument list.I think that at least part of this logic is intended to handle implicit method selection rather than overload resolution per se, but is misplaced and should actually be moved to
ImplicitSearch#improves
where, as well as fixing this bug, we have opportunities to improve implicit method selection based on implicit argument lists.This bug is also present in Dotty.
The text was updated successfully, but these errors were encountered: