Skip to content

Commit 210dbc7

Browse files
committed
SI-3346 implicit parameters can now guide implicit view inference
This simple patch makes it possible for implicit views to benefit from fundep-guided type inference, eliminating a nasty special case in implicit inference. Here are the changes that I had to apply to the tests (they exposed quite a number of interesting questions that I was happy to answer): 1) neg/t5845.scala now works, so I moved it to pos. That easily makes sense, because the test was just a canary to track previous state of things. 2) neg/divergent_implicit.scala, neg/t5578.scala and neg/t7519.scala changed their messages to less informative ones, because inapplicable implicit views now get disqualified early and therefore don't display their error messages to the user. This is an unfortunate but necessary byproduct of this change, and one can argue that the behavior is now completely consistent with implicit vals (that also don't explain why this or that implicit val got disqualified, but rather display a generic implicit value not found message). 3) scaladoc/implicits-chaining.scala and scaladoc/implicits-base.scala. Immediate culling of apriori inapplicable implicit views messes things up for Scaladoc, because it's interested in potentially applicable views, having infrastructure to track various constraints (type bounds, requirements for implicit parameters, etc) that guide applicability of views and then present it to the user. Therefore, when scaladoc is detected, implicit search reverts to the old behavior. 4) We still cannot have Jason's workaround to type constructor inference mentioned in comments to SI-3346, because it's not really about implicit parameters of implicit views, but about type inference flowing from the implicit parameter list to a preceding parameter list in order to affect inference of an implicit view. This is something that's still too ambitious.
1 parent acd7780 commit 210dbc7

29 files changed

+334
-24
lines changed

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -579,10 +579,10 @@ trait Implicits {
579579
private def typedImplicit1(info: ImplicitInfo, isLocal: Boolean): SearchResult = {
580580
if (Statistics.canEnable) Statistics.incCounter(matchingImplicits)
581581

582-
val itree = atPos(pos.focus) {
583-
// workaround for deficient context provided by ModelFactoryImplicitSupport#makeImplicitConstraints
584-
val isScalaDoc = context.tree == EmptyTree
582+
// workaround for deficient context provided by ModelFactoryImplicitSupport#makeImplicitConstraints
583+
val isScalaDoc = context.tree == EmptyTree
585584

585+
val itree = atPos(pos.focus) {
586586
if (isLocal && !isScalaDoc) {
587587
// SI-4270 SI-5376 Always use an unattributed Ident for implicits in the local scope,
588588
// rather than an attributed Select, to detect shadowing.
@@ -605,7 +605,23 @@ trait Implicits {
605605
atPos(itree.pos)(Apply(itree, List(Ident("<argument>") setType approximate(arg1)))),
606606
EXPRmode,
607607
approximate(arg2)
608-
)
608+
) match {
609+
// try to infer implicit parameters immediately in order to:
610+
// 1) guide type inference for implicit views
611+
// 2) discard ineligible views right away instead of risking spurious ambiguous implicits
612+
//
613+
// this is an improvement of the state of the art that brings consistency to implicit resolution rules
614+
// (and also helps fundep materialization to be applicable to implicit views)
615+
//
616+
// there's one caveat though. we need to turn this behavior off for scaladoc
617+
// because scaladoc usually doesn't know the entire story
618+
// and is just interested in views that are potentially applicable
619+
// for instance, if we have `class C[T]` and `implicit def conv[T: Numeric](c: C[T]) = ???`
620+
// then Scaladoc will give us something of type `C[T]`, and it would like to know
621+
// that `conv` is potentially available under such and such conditions
622+
case tree if isImplicitMethodType(tree.tpe) && !isScalaDoc => applyImplicitArgs(tree)
623+
case tree => tree
624+
}
609625
case _ => fallback
610626
}
611627
context.firstError match { // using match rather than foreach to avoid non local return.
@@ -617,7 +633,7 @@ trait Implicits {
617633

618634
if (Statistics.canEnable) Statistics.incCounter(typedImplicits)
619635

620-
val itree2 = if (isView) (itree1: @unchecked) match { case Apply(fun, _) => fun }
636+
val itree2 = if (isView) treeInfo.dissectApplied(itree1).callee
621637
else adapt(itree1, EXPRmode, wildPt)
622638

623639
typingStack.showAdapt(itree, itree2, pt, context)

test/files/neg/divergent-implicit.check

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@ divergent-implicit.scala:4: error: type mismatch;
33
required: String
44
val x1: String = 1
55
^
6-
divergent-implicit.scala:5: error: diverging implicit expansion for type Int => String
7-
starting with method cast in object Test1
8-
val x2: String = cast[Int, String](1)
9-
^
10-
divergent-implicit.scala:14: error: diverging implicit expansion for type Test2.Baz => Test2.Bar
11-
starting with method baz2bar in object Test2
6+
divergent-implicit.scala:14: error: type mismatch;
7+
found : Test2.Foo
8+
required: Test2.Bar
129
val x: Bar = new Foo
1310
^
14-
divergent-implicit.scala:15: error: diverging implicit expansion for type Test2.Foo => Test2.Bar
15-
starting with method foo2bar in object Test2
11+
divergent-implicit.scala:15: error: type mismatch;
12+
found : Test2.Baz
13+
required: Test2.Bar
1614
val y: Bar = new Baz
1715
^
18-
four errors found
16+
three errors found

test/files/neg/t3346b.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
t3346b.scala:14: error: could not find implicit value for evidence parameter of type TC[Any]
2+
val y = foo(1)
3+
^
4+
one error found

test/files/neg/t3346b.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.language.implicitConversions
2+
3+
trait T[X]
4+
trait U[X]
5+
trait TC[M[_]]
6+
7+
object Test extends App {
8+
def foo[M[_]: TC, A](ma: M[A]) = ()
9+
implicit val TCofT: TC[T] = new TC[T] {}
10+
implicit def any2T[A](a: A): T[A] = new T[A] {}
11+
implicit def any2U[A](a: A): U[A] = new U[A] {}
12+
13+
val x = foo[T, Int](1)
14+
val y = foo(1)
15+
}

test/files/neg/t3346c.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
t3346c.scala:60: error: value bar is not a member of Either[Int,String]
2+
eii.bar
3+
^
4+
one error found

test/files/neg/t3346c.scala

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
object Test extends App {
2+
//
3+
// An attempt to workaround SI-2712, foiled by SI-3346
4+
//
5+
trait TC[M[_]]
6+
7+
type EitherInt[A] = Either[Int, A]
8+
9+
implicit object EitherTC extends TC[EitherInt]
10+
11+
def foo[M[_]: TC, A](ma: M[A]) = ()
12+
13+
val eii: Either[Int, String] = Right("")
14+
15+
foo[EitherInt, String](eii)
16+
17+
// This one needs SI-2712 Higher Order Unification
18+
//foo(eii) // not inferred
19+
20+
// A workaround is to provide a set of implicit conversions that take values
21+
// based on type constructors of various shapes, and search for the
22+
// type class instances.
23+
//
24+
// This is the approach taken by scalaz7.
25+
26+
trait TCValue[M[_], A] {
27+
implicit def self: M[A]
28+
def M: TC[M]
29+
30+
// instead of `foo(eii)`, we'll try `eii.foo`
31+
def foo[M[_], A] = ()
32+
}
33+
34+
35+
implicit def ToTCValue[M[_], A](ma: M[A])(implicit M0: TC[M]) = new TCValue[M, A] {
36+
implicit val M = M0
37+
val self = ma
38+
}
39+
implicit def ToTCValueBin1[M[_, _], A, B](ma: M[A, B])(implicit M0: TC[({type λ[α]=M[A, α]})#λ]): TCValue[({type λ[α] = M[A, α]})#λ, B] = new TCValue[({type λ[α]=M[A, α]})#λ, B] {
40+
implicit val M = M0
41+
val self = ma
42+
}
43+
implicit def ToTCValueBin2[M[_, _], A, B](ma: M[A, B])(implicit M0: TC[({type λ[α]=M[α, B]})#λ]): TCValue[({type λ[α]=M[α, B]})#λ, A] = new TCValue[({type λ[α]=M[α, B]})#λ, A] {
44+
implicit val M = M0
45+
val self = ma
46+
}
47+
48+
49+
ToTCValueBin1(eii).foo
50+
51+
// as expected, could not find implicit parameter
52+
// ToTCValueBin2(eii).bar
53+
54+
// error: implicit conversions are not applicable because they are ambiguous, both method ToTCValueBin1 ... and method ToTCValueBin2
55+
// annoying!!
56+
// https://issues.scala-lang.org/browse/SI-3346
57+
//
58+
// Works if we remove ToTCValueBin2
59+
//
60+
eii.bar
61+
}

test/files/neg/t3346i.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
t3346i.scala:28: error: value a is not a member of Test.A[T]
2+
(new A).a
3+
^
4+
t3346i.scala:29: error: value a is not a member of Test.A[Nothing]
5+
(new A[Nothing]).a
6+
^
7+
two errors found

test/files/neg/t3346i.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import scala.language.implicitConversions
2+
3+
// the classes involved
4+
case class Z[U](a: U)
5+
case class Intermediate[T, U](t: T, u: U)
6+
class Implicit1[T](b: Implicit2[T])
7+
class Implicit2[T](c: Implicit3[T])
8+
class Implicit3[T](/* and so on */)
9+
10+
object Test extends App {
11+
// the base conversion
12+
implicit def convertToZ[T](a: A[T])(implicit b: Implicit1[T]): Z[A[T]] = Z(a)
13+
14+
// and the implicit chaining, don't you just love it? :D
15+
// implicit1, with one alternative
16+
implicit def implicit1[T <: Intermediate[_, _]](implicit b: Implicit2[T]) = new Implicit1[T](b)
17+
// implicit2, with two alternatives
18+
implicit def implicit2alt1[T <: Intermediate[_ <: String, _]](implicit c: Implicit3[T]) = new Implicit2[T](c)
19+
implicit def implicit2alt2[T <: Intermediate[_ <: Double, _]](implicit c: Implicit3[T]) = new Implicit2[T](c)
20+
// implicit3, with two alternatives
21+
implicit def implicit3alt1[T <: Intermediate[_, _ <: Int]] = new Implicit3[T]()
22+
implicit def implicit3alt2[T <: Intermediate[_ <: Double, _ <: AnyRef],X] = new Implicit3[T]()
23+
24+
// and our targets
25+
/** conversion here, with constraints */
26+
class A[T]()
27+
28+
(new A).a
29+
(new A[Nothing]).a
30+
}

test/files/neg/t5578.check

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
t5578.scala:33: error: No Manifest available for T.
1+
t5578.scala:33: error: type mismatch;
2+
found : NumericOpsExp.this.Plus[T]
3+
required: NumericOpsExp.this.Rep[T]
4+
(which expands to) NumericOpsExp.this.Exp[T]
25
def plus[T: Numeric](x: Rep[T], y: Rep[T]): Rep[T] = Plus[T](x,y)
36
^
47
one error found

test/files/neg/t5845.check

Lines changed: 0 additions & 7 deletions
This file was deleted.

test/files/neg/t7519.check

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
t7519.scala:5: error: could not find implicit value for parameter nada: Nothing
1+
t7519.scala:5: error: type mismatch;
2+
found : Int(0)
3+
required: String
24
locally(0 : String) // was: "value conversion is not a member of C.this.C"
35
^
4-
t7519.scala:15: error: could not find implicit value for parameter nada: Nothing
6+
t7519.scala:15: error: type mismatch;
7+
found : Int(0)
8+
required: String
59
locally(0 : String) // was: "value conversion is not a member of U"
610
^
711
two errors found

test/files/pos/t5845.check

Whitespace-only changes.
File renamed without changes.

test/files/run/t3346a.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1

test/files/run/t3346a.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.language.implicitConversions
2+
3+
object Test extends App {
4+
class Rep[T](x : T)
5+
6+
class SomeOps[T](x : Rep[T]) { def foo = 1 }
7+
implicit def mkOps[X, T](x : X)(implicit conv: X => Rep[T]) : SomeOps[T] = new SomeOps(conv(x))
8+
9+
val a: Rep[Int] = new Rep(42)
10+
println(a.foo)
11+
}

test/files/run/t3346b.check

Whitespace-only changes.

test/files/run/t3346c.check

Whitespace-only changes.

test/files/run/t3346d.check

Whitespace-only changes.

test/files/run/t3346d.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.language.implicitConversions
2+
3+
object Test extends App {
4+
trait TARInt
5+
6+
trait Basket[A,B] {
7+
def iAmABasket = {}
8+
}
9+
10+
trait BasketFactory[A,B] {
11+
def create(v: A): Basket[A,B]
12+
}
13+
14+
implicit val bf = new BasketFactory[Int,TARInt] {
15+
def create(v: Int): Basket[Int,TARInt] = new Basket[Int, TARInt]{}
16+
}
17+
18+
implicit def i2[A,B](a: A)(implicit bf: BasketFactory[A,B]): Basket[A,B] = bf.create(a)
19+
20+
1.iAmABasket // <-- i2 conversion not applicable
21+
}

test/files/run/t3346e.check

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
eqw
2+
List(0, 2)
3+
List(0, 2)
4+
BitSet(0, 2)
5+
Vector(113, 119, 101)
6+
qwe
7+
List(2, 0)
8+
List(0!)
9+
BitSet(0, 2)
10+
qwe
11+
List(2, 0)
12+
qwe

test/files/run/t3346e.scala

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import scala.language.implicitConversions
2+
import scala.collection.generic.CanBuildFrom
3+
import scala.math.Ordering
4+
import collection.{TraversableLike, SeqLike}
5+
import collection.immutable.BitSet
6+
7+
class QuickSort[Coll](a: Coll) {
8+
//should be able to sort only something with defined order (someting like a Seq)
9+
def quickSort[T](implicit ev0: Coll => SeqLike[T, Coll],
10+
cbf: CanBuildFrom[Coll, T, Coll],
11+
n: Ordering[T]): Coll = {
12+
quickSortAnything(ev0, cbf, n)
13+
}
14+
15+
//we can even sort a Set, if we really want to
16+
def quickSortAnything[T](implicit ev0: Coll => TraversableLike[T, Coll],
17+
cbf: CanBuildFrom[Coll, T, Coll],
18+
n: Ordering[T]): Coll = {
19+
import n._
20+
if (a.size < 2) {
21+
a
22+
} else {
23+
// We pick the first value for the pivot.
24+
val pivot = a.head
25+
val (lower, tmp) = a.partition(_ < pivot)
26+
val (upper, same) = tmp.partition(_ > pivot)
27+
val b = cbf()
28+
b.sizeHint(a.size)
29+
b ++= new QuickSort(lower).quickSortAnything
30+
b ++= same
31+
b ++= new QuickSort(upper).quickSortAnything
32+
b.result
33+
}
34+
}
35+
}
36+
37+
class FilterMap[Repr](a: Repr) {
38+
def filterMap[A, B, That](f: A => Option[B])(implicit ev0: Repr => TraversableLike[A, Repr],
39+
cbf: CanBuildFrom[Repr, B, That]): That = {
40+
a.flatMap(e => f(e).toSeq)
41+
}
42+
}
43+
44+
class FilterMapFixed[A, Repr <% TraversableLike[A, Repr]](a: Repr) {
45+
def filterMap2[B, That](f: A => Option[B])(implicit cbf: CanBuildFrom[Repr, B, That]): That = {
46+
a.flatMap(e => f(e).toSeq)
47+
}
48+
}
49+
50+
object MyEnhancements {
51+
implicit def toQS[Coll](a: Coll) = new QuickSort(a)
52+
implicit def toFM[Coll](a: Coll) = new FilterMap(a)
53+
implicit def toFM2[A, Repr <% TraversableLike[A, Repr]](a: Repr) = new FilterMapFixed(a)
54+
}
55+
56+
object Test extends App {
57+
58+
import MyEnhancements._
59+
60+
println("qwe".quickSort)
61+
println(Array(2, 0).quickSort.toList)
62+
println(Seq(2, 0).quickSort)
63+
//not very useful to sort a set, but just as a demonstration
64+
println(BitSet(2, 0).quickSortAnything)
65+
66+
//need to hint type inferencer,
67+
//probably will be able to overcome after https://issues.scala-lang.org/browse/SI-4699 and
68+
// related issues are fixed (by moving ev0 parameter from filterMap to toFM), see toFM2
69+
println("qwe".filterMap((c: Char) => Some(c.toInt)))
70+
println("qwe".filterMap((c: Char) => Some(c)))
71+
println(Array(2, 0).filterMap((c: Int) => Some(c.toInt)).toList)
72+
println(Seq(2, 0).filterMap((c: Int) => if (c < 2) Some(c + "!") else None))
73+
def test(i:Int) = Option(i)
74+
println(BitSet(2,0).filterMap(test))
75+
76+
println(toFM2("qwe").filterMap2(c => Some(c)))
77+
println(toFM2(Array(2, 0)).filterMap2(c => Some(c.toInt)).toList)
78+
//No implicit view available from java.lang.String => scala.collection.TraversableLike[A,java.lang.String]. :(
79+
//Not anymore :)
80+
println("qwe".filterMap2(c => Some(c)))
81+
}

test/files/run/t3346f.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
5
2+
5

test/files/run/t3346f.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.language.implicitConversions
2+
import scala.language.reflectiveCalls
3+
4+
object Test extends App {
5+
trait Foo[A]
6+
implicit def fooString: Foo[String] = null
7+
implicit def value[A](implicit foo: Foo[A]) = 5
8+
9+
println(implicitly[Int])
10+
11+
implicit def conversion[A](x: Int)(implicit foo: Foo[A]) = new {
12+
def aMethod = 5
13+
}
14+
println(1.aMethod)
15+
}

test/files/run/t3346g.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A(3,asdf)

test/files/run/t3346g.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.implicitConversions
2+
3+
case class A(b: Int, c: String)
4+
5+
object Test extends App {
6+
implicit def s2i(s: String): Int = s.length
7+
implicit def toA[T](t: T)(implicit f: T => Int): A = A(f(t), t.toString)
8+
println("asdf".copy(b = 3))
9+
}

test/files/run/t3346h.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x

0 commit comments

Comments
 (0)