Skip to content

Commit a0439b9

Browse files
committed
Fix #9185: Look at multiple search failures when trying an extension method
Also: change `implicitly[...]` to `summon[...]` in error message and issue "extension method was tried" addendums also when no implicits were searched.
1 parent b5b41b4 commit a0439b9

File tree

9 files changed

+80
-25
lines changed

9 files changed

+80
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
351351
case id: Trees.SearchFailureIdent[?] =>
352352
tree.typeOpt match {
353353
case reason: Implicits.SearchFailureType =>
354-
toText(id.name) ~ "implicitly[" ~ toText(reason.clarify(reason.expectedType)) ~ "]"
354+
toText(id.name) ~ "summon[" ~ toText(reason.clarify(reason.expectedType)) ~ "]"
355355
case _ =>
356356
toText(id.name)
357357
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ trait Showable extends Any {
2222
/** The string representation of this showable element. */
2323
def show(implicit ctx: Context): String = toText(ctx.printer).show
2424

25+
/** The string representation with each line after the first one indented
26+
* by the given given margin (in spaces).
27+
*/
28+
def showIndented(margin: Int)(using Context): String = show.replace("\n", "\n" + " " * margin)
29+
2530
/** The summarized string representation of this showable element.
2631
* Recursion depth is limited to some smallish value. Default is
2732
* Config.summarizeDepth.

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,10 @@ object Implicits {
478478
def explanation(using Context): String =
479479
em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify"
480480
}
481+
482+
class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType:
483+
def argument = EmptyTree
484+
def explanation(using Context) = em"$extApp does not $qualify"
481485
}
482486

483487
import Implicits._

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

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -151,27 +151,36 @@ trait TypeAssigner {
151151
else {
152152
val kind = if (name.isTypeName) "type" else "value"
153153
def addendum =
154-
if (qualType.derivesFrom(defn.DynamicClass))
154+
val attempts: List[Tree] = qual1.getAttachment(Typer.HiddenSearchFailure) match
155+
case Some(failures) =>
156+
for failure <- failures
157+
if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits]
158+
yield failure.tree
159+
case _ => Nil
160+
if qualType.derivesFrom(defn.DynamicClass) then
155161
"\npossible cause: maybe a wrong Dynamic method signature?"
156-
else qual1.getAttachment(Typer.HiddenSearchFailure) match
157-
case Some(failure) if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] =>
158-
i""".
159-
|An extension method was tried, but could not be fully constructed:
160-
|
161-
| ${failure.tree.show.replace("\n", "\n ")}"""
162-
case _ =>
163-
if (tree.hasAttachment(desugar.MultiLineInfix))
164-
i""".
165-
|Note that `$name` is treated as an infix operator in Scala 3.
166-
|If you do not want that, insert a `;` or empty line in front
167-
|or drop any spaces behind the operator."""
168-
else if qualType.isBottomType then ""
169-
else
170-
var add = importSuggestionAddendum(
171-
ViewProto(qualType.widen,
172-
SelectionProto(name, WildcardType, NoViewsAllowed, privateOK = false)))
173-
if add.isEmpty then ""
174-
else ", but could be made available as an extension method." ++ add
162+
else if attempts.nonEmpty then
163+
val extMethods =
164+
if attempts.length > 1 then "Extension methods were"
165+
else "An extension method was"
166+
val attemptStrings = attempts.map(_.showIndented(4))
167+
i""".
168+
|$extMethods tried, but could not be fully constructed:
169+
|
170+
| $attemptStrings%\nor\n %"""
171+
else if tree.hasAttachment(desugar.MultiLineInfix) then
172+
i""".
173+
|Note that `$name` is treated as an infix operator in Scala 3.
174+
|If you do not want that, insert a `;` or empty line in front
175+
|or drop any spaces behind the operator."""
176+
else if qualType.isBottomType then
177+
""
178+
else
179+
val add = importSuggestionAddendum(
180+
ViewProto(qualType.widen,
181+
SelectionProto(name, WildcardType, NoViewsAllowed, privateOK = false)))
182+
if add.isEmpty then ""
183+
else ", but could be made available as an extension method." ++ add
175184
end addendum
176185
errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos)
177186
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ object Typer {
7979
/** An attachment that indicates a failed conversion or extension method
8080
* search was tried on a tree. This will in some cases be reported in error messages
8181
*/
82-
private[typer] val HiddenSearchFailure = new Property.Key[SearchFailure]
82+
private[typer] val HiddenSearchFailure = new Property.Key[List[SearchFailure]]
83+
84+
/** Add `fail` to the list of search failures attached to `tree` */
85+
def rememberSearchFailure(tree: tpd.Tree, fail: SearchFailure) =
86+
tree.putAttachment(HiddenSearchFailure,
87+
fail :: tree.attachmentOrElse(HiddenSearchFailure, Nil))
8388
}
8489
class Typer extends Namer
8590
with TypeAssigner
@@ -3393,6 +3398,8 @@ class Typer extends Namer
33933398
nestedCtx.typerState.commit()
33943399
return ExtMethodApply(app)
33953400
}
3401+
else if !app.isEmpty then
3402+
rememberSearchFailure(tree, SearchFailure(app.withType(FailedExtension(app, pt))))
33963403
case _ =>
33973404
}
33983405

@@ -3417,7 +3424,7 @@ class Typer extends Namer
34173424
// will cause a failure at the next level out, which usually gives
34183425
// a better error message. To compensate, store the encountered failure
34193426
// as an attachment, so that it can be reported later as an addendum.
3420-
tree.putAttachment(HiddenSearchFailure, failure)
3427+
rememberSearchFailure(tree, failure)
34213428
tree
34223429
}
34233430
else recover(failure.reason)

tests/neg/i9185.check

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- [E008] Not Found Error: tests/neg/i9185.scala:7:21 ------------------------------------------------------------------
2+
7 | val value2 = "ola".pure // error
3+
| ^^^^^^^^^^
4+
| value pure is not a member of String.
5+
| An extension method was tried, but could not be fully constructed:
6+
|
7+
| M.pure[A, F]("ola")(/* ambiguous */summon[M[F]])
8+
-- Error: tests/neg/i9185.scala:8:26 -----------------------------------------------------------------------------------
9+
8 | val value3 = pure("ola") // error
10+
| ^
11+
|ambiguous implicit arguments: both object listMonad in object M and object optionMonad in object M match type M[F] of parameter m of method pure in object M
12+
-- [E008] Not Found Error: tests/neg/i9185.scala:11:16 -----------------------------------------------------------------
13+
11 | val l = "abc".len // error
14+
| ^^^^^^^^^
15+
| value len is not a member of String.
16+
| An extension method was tried, but could not be fully constructed:
17+
|
18+
| M.len("abc")

tests/neg/i9185.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
trait M[F[_]] { def pure[A](x: A): F[A] }
2+
object M {
3+
def [A, F[A]](x: A).pure(using m: M[F]): F[A] = m.pure(x)
4+
given listMonad as M[List] { def pure[A](x: A): List[A] = List(x) }
5+
given optionMonad as M[Option] { def pure[A](x: A): Option[A] = Some(x) }
6+
val value1: List[String] = "ola".pure
7+
val value2 = "ola".pure // error
8+
val value3 = pure("ola") // error
9+
10+
def (x: Int).len: Int = x
11+
val l = "abc".len // error
12+
}

tests/neg/implicitSearch.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
| no implicit argument of type Test.Ord[List[List[T]]] was found for parameter o of method sort in object Test.
55
| I found:
66
|
7-
| Test.listOrd[T](Test.listOrd[T](/* missing */implicitly[Test.Ord[T]]))
7+
| Test.listOrd[T](Test.listOrd[T](/* missing */summon[Test.Ord[T]]))
88
|
99
| But no implicit values were found that match type Test.Ord[T].
1010
-- Error: tests/neg/implicitSearch.scala:15:38 -------------------------------------------------------------------------

tests/neg/missing-implicit3.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
|no implicit argument of type ord.Ord[ord.Foo] was found for an implicit parameter of method sort in package ord.
55
|I found:
66
|
7-
| ord.Ord.ordered[A](/* missing */implicitly[ord.Foo => Comparable[? >: ord.Foo]])
7+
| ord.Ord.ordered[A](/* missing */summon[ord.Foo => Comparable[? >: ord.Foo]])
88
|
99
|But no implicit values were found that match type ord.Foo => Comparable[? >: ord.Foo].

0 commit comments

Comments
 (0)