-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Take implicit parameters into account for is-as-specific computations #5925
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
Take implicit parameters into account for is-as-specific computations #5925
Conversation
c3a7b23
to
b1a0234
Compare
@milessabin Yes, that's what I implemented first. But it runs afoul of the problem I mentioned. The difference between the approaches is that I now use implicit parameters only as a final tie breaker, after both type specificity and location are taken into account. |
@odersky If I can make it work for that case would you be open to going in that direction? |
6264a67
to
b17435f
Compare
Possibly, but I quite like the simplicity of the current solution (both implementation and spec-wise). What would we gain by changing it? |
Here's the current text:
|
Without it we miss out on things like this (from the Scala 2 PR), 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)
} |
OK, I see now. We need not just the number but also the types of the implicit parameters to influence priority. We could probably do this also as a final tie break, however. I like to push these tie breaks back in the sequence of decisions not just for backwards compatibility but also for complexity. Overloading comparisons can be a significant part of compile-time (I believe for ScalaTest ~20%) and they can explode combinatorically. The current implementation tries very hard to minimize the number of specificity comparisons for types. If we do more complex checks only as a last resort when we would report an ambiguity otherwise, we know that they will be called only rarely. |
A modified^2 version could be:
Would that work? |
Where "relative weight" is the specificity of the result type? Yes, I think that works :-) |
Relative weight is the outcome of the combined specificity / ownership test. You get one weight point for "as specific as" and one weight point for "owned by a subclass (or its companion object)". |
b17435f
to
2234c2b
Compare
Number of implicit parameters is applied as a final tie break, if two alternatives would be otherwise equally specific. I tried to roll them into type specificity first, but this fails the dotc bootstrap where we have: def name(implicit ctx: Context): Name in class Denotation val name: Name in subclass SymDenotation The `val name` wins, since it is defined in a subclass and the result types are the same. If we let the type `(implicit ctx: Context): Name` win over `Name` this would be ambiguous instead.
Update doc on implicit resolution changes to take the previous changes in scala#5887 into account.
When testing specificity of methods, widen result type of synthetic implied methods. I tried widening the inferred result type of these methods already in Namer instead, but this causes problems since it can hide extension methods.
If both alternatives have same weights and same number of implicit parameters, treat them as normal (non-implicit) methods and try again.
2234c2b
to
2f00820
Compare
@milessabin The changed spec is in Can you take a look whether we can leave it like this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is absolutely spot on ... it's a huge improvement over Scala 2 and addresses pretty much all of my issues :-)
Number of implicit parameters is applied as a final tie break, if
two alternatives would be otherwise equally specific.
I tried to roll them into type specificity first, but this fails
the dotc bootstrap where we have:
The
val name
wins, since it is defined in a subclass and the result typesare the same. If we let the type
(implicit ctx: Context): Name
win overName
this would be ambiguous instead.