Skip to content

Commit 58746bf

Browse files
committed
Improve error diagnostics for implicits not found
More work was needed to exactly pinpoint what implicit was missing.
1 parent 86b4acc commit 58746bf

File tree

5 files changed

+306
-6
lines changed

5 files changed

+306
-6
lines changed

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ object OrderingConstraint {
102102
newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries))
103103
def initial = Nil
104104
}
105+
106+
val empty = new OrderingConstraint(SimpleIdentityMap.Empty, SimpleIdentityMap.Empty, SimpleIdentityMap.Empty)
105107
}
106108

107109
import OrderingConstraint._

compiler/src/dotty/tools/dotc/core/TyperState.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class TyperState(previous: TyperState /* | Null */) {
3030
def setReporter(reporter: Reporter): this.type = { myReporter = reporter; this }
3131

3232
private[this] var myConstraint: Constraint =
33-
if (previous == null) new OrderingConstraint(SimpleIdentityMap.Empty, SimpleIdentityMap.Empty, SimpleIdentityMap.Empty)
33+
if (previous == null) OrderingConstraint.empty
3434
else previous.constraint
3535

3636
def constraint: Constraint = myConstraint

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
303303
case id: Trees.SearchFailureIdent[_] =>
304304
tree.typeOpt match {
305305
case reason: Implicits.SearchFailureType =>
306-
toText(id.name) ~ "implicitly[" ~ toText(reason.expectedType) ~ "]"
306+
toText(id.name) ~ "implicitly[" ~ toText(reason.clarify(reason.expectedType)) ~ "]"
307307
case _ =>
308308
toText(id.name)
309309
}

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,13 @@ object Implicits {
326326
def expectedType: Type
327327
protected def argument: Tree
328328

329+
/** A "massaging" function for displayed types to give better info in error diagnostics */
330+
def clarify(tp: Type)(implicit ctx: Context): Type = tp
331+
329332
final protected def qualify(implicit ctx: Context): String =
330333
if (expectedType.exists)
331-
if (argument.isEmpty) em"match type $expectedType"
332-
else em"convert from ${argument.tpe} to $expectedType"
334+
if (argument.isEmpty) em"match type ${clarify(expectedType)}"
335+
else em"convert from ${argument.tpe} to ${clarify(expectedType)}"
333336
else
334337
if (argument.isEmpty) em"match expected type"
335338
else em"convert from ${argument.tpe} to expected type"
@@ -345,12 +348,35 @@ object Implicits {
345348
def whyNoConversion(implicit ctx: Context): String = ""
346349
}
347350

348-
class NoMatchingImplicits(val expectedType: Type, val argument: Tree) extends SearchFailureType {
351+
class NoMatchingImplicits(val expectedType: Type, val argument: Tree, constraint: Constraint = OrderingConstraint.empty) extends SearchFailureType {
352+
353+
/** Replace all type parameters in constraint by their bounds, to make it clearer
354+
* what was expected
355+
*/
356+
override def clarify(tp: Type)(implicit ctx: Context) = {
357+
val map = new TypeMap {
358+
def apply(t: Type): Type = t match {
359+
case t: TypeParamRef =>
360+
constraint.entry(t) match {
361+
case NoType => t
362+
case bounds: TypeBounds => constraint.fullBounds(t)
363+
case t1 => t1
364+
}
365+
case t: TypeVar =>
366+
t.instanceOpt.orElse(apply(t.origin))
367+
case _ =>
368+
mapOver(t)
369+
}
370+
}
371+
map(tp)
372+
}
373+
349374
def explanation(implicit ctx: Context): String =
350375
em"no implicit values were found that $qualify"
376+
override def toString = s"NoMatchingImplicits($expectedType, $argument)"
351377
}
352378

353-
@sharable object NoMatchingImplicits extends NoMatchingImplicits(NoType, EmptyTree)
379+
@sharable object NoMatchingImplicits extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty)
354380

355381
@sharable val NoMatchingImplicitsFailure: SearchFailure =
356382
SearchFailure(NoMatchingImplicits)
@@ -885,6 +911,8 @@ trait Implicits { self: Typer =>
885911
}
886912
}
887913
else result
914+
case NoMatchingImplicitsFailure =>
915+
SearchFailure(new NoMatchingImplicits(pt, argument, ctx.typerState.constraint))
888916
case _ =>
889917
result
890918
}

tests/neg/typeclass-derivation2.scala

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import scala.collection.mutable
2+
import scala.annotation.tailrec
3+
4+
trait Deriving {
5+
import Deriving._
6+
7+
/** A mirror of case with ordinal number `ordinal` and elements as given by `Product` */
8+
def mirror(ordinal: Int, product: Product): Mirror =
9+
new Mirror(this, ordinal, product)
10+
11+
/** A mirror with elements given as an array */
12+
def mirror(ordinal: Int, elems: Array[AnyRef]): Mirror =
13+
mirror(ordinal, new ArrayProduct(elems))
14+
15+
/** A mirror with an initial empty array of `numElems` elements, to be filled in. */
16+
def mirror(ordinal: Int, numElems: Int): Mirror =
17+
mirror(ordinal, new Array[AnyRef](numElems))
18+
19+
/** A mirror of a case with no elements */
20+
def mirror(ordinal: Int): Mirror =
21+
mirror(ordinal, EmptyProduct)
22+
23+
/** The case and element labels of the described ADT as encoded strings. */
24+
protected def caseLabels: Array[String]
25+
26+
private final val separator = '\000'
27+
28+
private def label(ordinal: Int, idx: Int): String = {
29+
val labels = caseLabels(ordinal)
30+
@tailrec def separatorPos(from: Int): Int =
31+
if (from == labels.length || labels(from) == separator) from
32+
else separatorPos(from + 1)
33+
@tailrec def findLabel(count: Int, idx: Int): String =
34+
if (idx == labels.length) ""
35+
else if (count == 0) labels.substring(idx, separatorPos(idx))
36+
else findLabel(if (labels(idx) == separator) count - 1 else count, idx + 1)
37+
findLabel(idx, 0)
38+
}
39+
}
40+
41+
// Generic deriving infrastructure
42+
object Deriving {
43+
44+
/** A generic representation of a case in an ADT
45+
* @param deriving The companion object of the ADT
46+
* @param ordinal The ordinal value of the case in the list of the ADT's cases
47+
* @param elems The elements of the case
48+
*/
49+
class Mirror(val deriving: Deriving, val ordinal: Int, val elems: Product) {
50+
51+
/** The `n`'th element of this generic case */
52+
def apply(n: Int): Any = elems.productElement(n)
53+
54+
/** The name of the constructor of the case reflected by this mirror */
55+
def caseLabel: String = deriving.label(ordinal, 0)
56+
57+
/** The label of the `n`'th element of the case reflected by this mirror */
58+
def elementLabel(n: Int) = deriving.label(ordinal, n + 1)
59+
}
60+
61+
/** A class for mapping between an ADT value and
62+
* the case mirror that represents the value.
63+
*/
64+
abstract class Reflected[T] {
65+
66+
/** The case mirror corresponding to ADT instance `x` */
67+
def reflect(x: T): Mirror
68+
69+
/** The ADT instance corresponding to given `mirror` */
70+
def reify(mirror: Mirror): T
71+
72+
/** The companion object of the ADT */
73+
def deriving: Deriving
74+
}
75+
76+
/** The shape of an ADT.
77+
* This is eithe a product (`Case`) or a sum (`Cases`) of products.
78+
*/
79+
enum Shape {
80+
81+
/** A sum with alternative types `Alts` */
82+
case Cases[Alts <: Tuple]
83+
84+
/** A product type `T` with element types `Elems` */
85+
case Case[T, Elems <: Tuple]
86+
}
87+
88+
/** Every generic derivation starts with a typeclass instance of this type.
89+
* It informs that type `T` has shape `S` and also implements runtime reflection on `T`.
90+
*/
91+
abstract class Shaped[T, S <: Shape] extends Reflected[T]
92+
93+
/** Helper class to turn arrays into products */
94+
private class ArrayProduct(val elems: Array[AnyRef]) extends Product {
95+
def canEqual(that: Any): Boolean = true
96+
def productElement(n: Int) = elems(n)
97+
def productArity = elems.length
98+
override def productIterator: Iterator[Any] = elems.iterator
99+
def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef]
100+
}
101+
102+
/** Helper object */
103+
private object EmptyProduct extends Product {
104+
def canEqual(that: Any): Boolean = true
105+
def productElement(n: Int) = throw new IndexOutOfBoundsException
106+
def productArity = 0
107+
}
108+
}
109+
110+
// An algebraic datatype
111+
enum Lst[+T] {
112+
case Cons(hd: T, tl: Lst[T])
113+
case Nil
114+
}
115+
116+
object Lst extends Deriving {
117+
// common compiler-generated infrastructure
118+
import Deriving._
119+
120+
type Shape[T] = Shape.Cases[(
121+
Shape.Case[Cons[T], (T, Lst[T])],
122+
Shape.Case[Nil.type, Unit]
123+
)]
124+
125+
val NilMirror = mirror(1)
126+
127+
implicit def lstShape[T]: Shaped[Lst[T], Shape[T]] = new {
128+
def reflect(xs: Lst[T]): Mirror = xs match {
129+
case xs: Cons[T] => mirror(0, xs)
130+
case Nil => NilMirror
131+
}
132+
def reify(c: Mirror): Lst[T] = c.ordinal match {
133+
case 0 => Cons[T](c(0).asInstanceOf, c(1).asInstanceOf)
134+
case 1 => Nil
135+
}
136+
def deriving = Lst
137+
}
138+
139+
protected val caseLabels = Array("Cons\000hd\000tl", "Nil")
140+
141+
// three clauses that could be generated from a `derives` clause
142+
implicit def LstShow[T: Show]: Show[Lst[T]] = Show.derived
143+
}
144+
145+
// A simple product type
146+
case class Pair[T](x: T, y: T)
147+
148+
object Pair extends Deriving {
149+
// common compiler-generated infrastructure
150+
import Deriving._
151+
152+
type Shape[T] = Shape.Case[Pair[T], (T, T)]
153+
154+
implicit def pairShape[T]: Shaped[Pair[T], Shape[T]] = new {
155+
def reflect(xy: Pair[T]) =
156+
mirror(0, xy)
157+
def reify(c: Mirror): Pair[T] =
158+
Pair(c(0).asInstanceOf, c(1).asInstanceOf)
159+
def deriving = Pair
160+
}
161+
162+
protected val caseLabels = Array("Pair\000x\000y")
163+
}
164+
165+
sealed trait Either[+L, +R] extends Product derives Eq, Pickler
166+
case class Left[L](x: L) extends Either[L, Nothing]
167+
case class Right[R](x: R) extends Either[Nothing, R]
168+
169+
object Either extends Deriving {
170+
import Deriving._
171+
172+
type Shape[L, R] = Shape.Cases[(
173+
Shape.Case[Left[L], L *: Unit],
174+
Shape.Case[Right[R], R *: Unit]
175+
)]
176+
177+
implicit def eitherShape[L, R]: Shaped[Either[L, R], Shape[L, R]] = new {
178+
def reflect(e: Either[L, R]): Mirror = e match {
179+
case e: Left[L] => mirror(0, e)
180+
case e: Right[R] => mirror(1, e)
181+
}
182+
def reify(c: Mirror): Either[L, R] = c.ordinal match {
183+
case 0 => Left[L](c(0).asInstanceOf)
184+
case 1 => Right[R](c(0).asInstanceOf)
185+
}
186+
def deriving = Either
187+
}
188+
189+
protected val caseLabels = Array("Left\000x", "Right\000x")
190+
191+
implicit def EitherShow[L: Show, R: Show]: Show[Either[L, R]] = Show.derived
192+
}
193+
194+
trait Show[T] {
195+
def show(x: T): String
196+
}
197+
object Show {
198+
import scala.typelevel._
199+
import Deriving._
200+
201+
inline def tryShow[T](x: T): String = implicit match {
202+
case s: Show[T] => s.show(x)
203+
}
204+
205+
inline def showElems[Elems <: Tuple](elems: Mirror, n: Int): List[String] =
206+
inline erasedValue[Elems] match {
207+
case _: (elem *: elems1) =>
208+
val formal = elems.elementLabel(n)
209+
val actual = tryShow[elem](elems(n).asInstanceOf)
210+
s"$formal = $actual" :: showElems[elems1](elems, n + 1)
211+
case _: Unit =>
212+
Nil
213+
}
214+
215+
inline def showCase[T, Elems <: Tuple](r: Reflected[T], x: T): String = {
216+
val mirror = r.reflect(x)
217+
val args = showElems[Elems](mirror, 0).mkString(", ")
218+
s"${mirror.caseLabel}($args)"
219+
}
220+
221+
inline def showCases[T, Alts <: Tuple](r: Reflected[T], x: T): String =
222+
inline erasedValue[Alts] match {
223+
case _: (Shape.Case[alt, elems] *: alts1) =>
224+
x match {
225+
case x: `alt` => showCase[T, elems](r, x)
226+
case _ => showCases[T, alts1](r, x)
227+
}
228+
case _: Unit =>
229+
throw new MatchError(x)
230+
}
231+
232+
inline def derived[T, S <: Shape](implicit ev: Shaped[T, S]): Show[T] = new {
233+
def show(x: T): String = inline erasedValue[S] match {
234+
case _: Shape.Cases[alts] =>
235+
showCases[T, alts](ev, x)
236+
case _: Shape.Case[_, elems] =>
237+
showCase[T, elems](ev, x)
238+
}
239+
}
240+
241+
implicit object IntShow extends Show[Int] {
242+
def show(x: Int): String = x.toString
243+
}
244+
}
245+
246+
// Tests
247+
object Test extends App {
248+
import Deriving._
249+
250+
def showPrintln[T: Show](x: T): Unit =
251+
println(implicitly[Show[T]].show(x))
252+
253+
val zs = Lst.Cons(Left(1), Lst.Cons(Right(Pair(2, 3)), Lst.Nil))
254+
showPrintln(zs) // error
255+
/* This should print as follows:
256+
-- Error: typeclass-derivation2.scala:254:17 -----------------------------------
257+
254 | showPrintln(zs) // error
258+
| ^
259+
|no implicit argument of type Show[Lst[Either[Int, Pair[Int]]]] was found for parameter evidence$5 of method showPrintln in object Test.
260+
|I found:
261+
|
262+
| Lst.LstShow[T](
263+
| Either.EitherShow[Int, Pair[Int]](Show.IntShow,
264+
| /* missing */implicitly[Show[Pair[Int]]]
265+
| )
266+
| )
267+
|
268+
|But no implicit values were found that match type Show[Pair[Int]].
269+
*/
270+
}

0 commit comments

Comments
 (0)