Skip to content

Commit 889aa61

Browse files
committed
Better diagnostics for too unspecific implicit searches
Fixes #15998
1 parent a503b7a commit 889aa61

File tree

4 files changed

+71
-8
lines changed

4 files changed

+71
-8
lines changed

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,25 +125,25 @@ object ErrorReporting {
125125
def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailureType = NoMatchingImplicits): Tree = {
126126
val normTp = normalize(tree.tpe, pt)
127127
val normPt = normalize(pt, pt)
128-
128+
129129
def contextFunctionCount(tp: Type): Int = tp.stripped match
130130
case defn.ContextFunctionType(_, restp, _) => 1 + contextFunctionCount(restp)
131131
case _ => 0
132132
def strippedTpCount = contextFunctionCount(tree.tpe) - contextFunctionCount(normTp)
133133
def strippedPtCount = contextFunctionCount(pt) - contextFunctionCount(normPt)
134-
134+
135135
val (treeTp, expectedTp) =
136136
if normTp <:< normPt || strippedTpCount != strippedPtCount
137137
then (tree.tpe, pt)
138138
else (normTp, normPt)
139139
// use normalized types if that also shows an error, and both sides stripped
140140
// the same number of context functions. Use original types otherwise.
141-
141+
142142
def missingElse = tree match
143143
case If(_, _, elsep @ Literal(Constant(()))) if elsep.span.isSynthetic =>
144144
"\nMaybe you are missing an else part for the conditional?"
145145
case _ => ""
146-
146+
147147
errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), implicitFailure.whyNoConversion, missingElse))
148148
}
149149

@@ -262,6 +262,9 @@ class ImplicitSearchError(
262262
case _ =>
263263
defaultAmbiguousImplicitMsg(ambi)
264264
}
265+
case ambi @ TooUnspecific(target) =>
266+
ex"""No implicit search was attempted${location("for")}
267+
|since the expected type $target is too unspecific"""
265268
case _ =>
266269
val shortMessage = userDefinedImplicitNotFoundParamMessage
267270
.orElse(userDefinedImplicitNotFoundTypeMessage)

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -411,14 +411,14 @@ object Implicits:
411411

412412
/** A failed search */
413413
case class SearchFailure(tree: Tree) extends SearchResult {
414-
final def isAmbiguous: Boolean = tree.tpe.isInstanceOf[AmbiguousImplicits]
414+
final def isAmbiguous: Boolean = tree.tpe.isInstanceOf[AmbiguousImplicits | TooUnspecific]
415415
final def reason: SearchFailureType = tree.tpe.asInstanceOf[SearchFailureType]
416416
}
417417

418418
object SearchFailure {
419419
def apply(tpe: SearchFailureType, span: Span)(using Context): SearchFailure = {
420420
val id = tpe match
421-
case tpe: AmbiguousImplicits =>
421+
case tpe: (AmbiguousImplicits | TooUnspecific) =>
422422
untpd.SearchFailureIdent(nme.AMBIGUOUS, s"/* ambiguous: ${tpe.explanation} */")
423423
case _ =>
424424
untpd.SearchFailureIdent(nme.MISSING, "/* missing */")
@@ -504,11 +504,14 @@ object Implicits:
504504
SearchFailure(ImplicitSearchTooLarge, NoSpan)(using NoContext)
505505

506506
/** A failure value indicating that an implicit search for a conversion was not tried */
507-
class TooUnspecific(target: Type) extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty):
507+
case class TooUnspecific(target: Type) extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty):
508508
override def whyNoConversion(using Context): String =
509509
i"""
510510
|Note that implicit conversions were not tried because the result of an implicit conversion
511511
|must be more specific than $target"""
512+
override def explanation(using Context) =
513+
i"""${super.explanation}.
514+
|The expected type $target is not specific enough, so no search was attempted"""
512515
override def toString = s"TooUnspecific"
513516

514517
/** An ambiguous implicits failure */
@@ -1484,7 +1487,7 @@ trait Implicits:
14841487

14851488
private def searchImplicit(contextual: Boolean): SearchResult =
14861489
if isUnderspecified(wildProto) then
1487-
NoMatchingImplicitsFailure
1490+
SearchFailure(TooUnspecific(pt), span)
14881491
else
14891492
val eligible =
14901493
if contextual then

tests/neg/i15998.check

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i15998.scala:11:23 ------------------------------------------------------------
2+
11 | RingSeq.isRotationOf("DAB") // error
3+
| ^^^^^
4+
| Found: ("DAB" : String)
5+
| Required: CC[A]
6+
|
7+
| where: A is a type variable
8+
| CC is a type variable with constraint <: [B] =>> collection.SeqOps[B, CC, CC[B]]
9+
|
10+
| Note that implicit conversions were not tried because the result of an implicit conversion
11+
| must be more specific than CC[A]
12+
|
13+
| longer explanation available when compiling with `-explain`
14+
-- [E008] Not Found Error: tests/neg/i15998.scala:12:9 -----------------------------------------------------------------
15+
12 | "ABCD".isRotationOf("DAB") // error
16+
| ^^^^^^^^^^^^^^^^^^^
17+
| value isRotationOf is not a member of String.
18+
| An extension method was tried, but could not be fully constructed:
19+
|
20+
| RingSeq.isRotationOf[A, CC]("ABCD") failed with
21+
|
22+
| Found: ("ABCD" : String)
23+
| Required: CC[A]
24+
|
25+
| where: A is a type variable
26+
| CC is a type variable with constraint <: [B] =>> collection.SeqOps[B, CC, CC[B]]
27+
|
28+
| Note that implicit conversions were not tried because the result of an implicit conversion
29+
| must be more specific than CC[A]
30+
-- Error: tests/neg/i15998.scala:21:13 ---------------------------------------------------------------------------------
31+
21 | val x = foo // error
32+
| ^
33+
| No implicit search was attempted for parameter x of method foo
34+
| since the expected type X is too unspecific
35+
|
36+
| where: X is a type variable

tests/neg/i15998.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.collection.SeqOps
2+
3+
trait ComparingOps:
4+
extension[A, CC[B] <: SeqOps[B, CC, CC[B]]](ring: CC[A])
5+
def isRotationOf(that: CC[A]): Boolean = ???
6+
7+
object RingSeq extends ComparingOps
8+
import RingSeq.*
9+
10+
@main def Test =
11+
RingSeq.isRotationOf("DAB") // error
12+
"ABCD".isRotationOf("DAB") // error
13+
14+
// workaround
15+
RingSeq.isRotationOf[Char, IndexedSeq]("DAB")
16+
RingSeq.isRotationOf(wrapString("DAB"))
17+
wrapString("ABCD").isRotationOf("DAB")
18+
19+
def foo[X](using x: X): X = x
20+
21+
val x = foo // error

0 commit comments

Comments
 (0)