Skip to content

Commit e0de959

Browse files
authored
Merge pull request #9189 from dotty-staging/fix-#9185
Fix #9185: Look at multiple failures when trying an extension method
2 parents ebe223e + 70eb093 commit e0de959

16 files changed

+184
-109
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ 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)
355+
~ ("summon[" ~ toText(reason.clarify(reason.expectedType)) ~ "]").close
355356
case _ =>
356357
toText(id.name)
357358
}

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/ErrorReporting.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,41 @@ object ErrorReporting {
144144
def rewriteNotice: String =
145145
if Feature.migrateTo3 then "\nThis patch can be inserted automatically under -rewrite."
146146
else ""
147+
148+
def selectErrorAddendum
149+
(tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String)
150+
(using Context): String =
151+
val attempts: List[Tree] = qual1.getAttachment(Typer.HiddenSearchFailure) match
152+
case Some(failures) =>
153+
for failure <- failures
154+
if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits]
155+
yield failure.tree
156+
case _ => Nil
157+
if qualType.derivesFrom(defn.DynamicClass) then
158+
"\npossible cause: maybe a wrong Dynamic method signature?"
159+
else if attempts.nonEmpty then
160+
val extMethods =
161+
if attempts.length > 1 then "Extension methods were"
162+
else "An extension method was"
163+
val attemptStrings = attempts.map(_.showIndented(4))
164+
i""".
165+
|$extMethods tried, but could not be fully constructed:
166+
|
167+
| $attemptStrings%\nor\n %"""
168+
else if tree.hasAttachment(desugar.MultiLineInfix) then
169+
i""".
170+
|Note that `${tree.name}` is treated as an infix operator in Scala 3.
171+
|If you do not want that, insert a `;` or empty line in front
172+
|or drop any spaces behind the operator."""
173+
else if qualType.isBottomType then
174+
""
175+
else
176+
val add = suggestImports(
177+
ViewProto(qualType.widen,
178+
SelectionProto(tree.name, WildcardType, NoViewsAllowed, privateOK = false)))
179+
if add.isEmpty then ""
180+
else ", but could be made available as an extension method." ++ add
181+
end selectErrorAddendum
147182
}
148183

149184
def dependentStr =

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,10 @@ object Implicits {
379379
}
380380

381381
object SearchFailure {
382-
def apply(tpe: SearchFailureType)(implicit src: SourceFile): SearchFailure = {
383-
val id =
384-
if (tpe.isInstanceOf[AmbiguousImplicits]) "/* ambiguous */"
385-
else "/* missing */"
382+
def apply(tpe: SearchFailureType)(using Context): SearchFailure = {
383+
val id = tpe match
384+
case tpe: AmbiguousImplicits => i"/* ambiguous: ${tpe.explanation} */"
385+
case _ => "/* missing */"
386386
SearchFailure(untpd.SearchFailureIdent(id.toTermName).withTypeUnchecked(tpe))
387387
}
388388
}
@@ -451,7 +451,7 @@ object Implicits {
451451
@sharable object NoMatchingImplicits extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty)
452452

453453
@sharable val NoMatchingImplicitsFailure: SearchFailure =
454-
SearchFailure(NoMatchingImplicits)(NoSource)
454+
SearchFailure(NoMatchingImplicits)(using NoContext)
455455

456456
/** An ambiguous implicits failure */
457457
class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType {
@@ -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: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -150,29 +150,7 @@ trait TypeAssigner {
150150
errorType(ex"$qualType does not have a constructor", tree.sourcePos)
151151
else {
152152
val kind = if (name.isTypeName) "type" else "value"
153-
def addendum =
154-
if (qualType.derivesFrom(defn.DynamicClass))
155-
"\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
175-
end addendum
153+
def addendum = err.selectErrorAddendum(tree, qual1, qualType, importSuggestionAddendum)
176154
errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos)
177155
}
178156
}

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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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")(
8+
| /* ambiguous: both object listMonad in object M and object optionMonad in object M match type M[F] */summon[M[F]]
9+
| )
10+
-- Error: tests/neg/i9185.scala:8:26 -----------------------------------------------------------------------------------
11+
8 | val value3 = pure("ola") // error
12+
| ^
13+
|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
14+
-- [E008] Not Found Error: tests/neg/i9185.scala:11:16 -----------------------------------------------------------------
15+
11 | val l = "abc".len // error
16+
| ^^^^^^^^^
17+
| value len is not a member of String.
18+
| An extension method was tried, but could not be fully constructed:
19+
|
20+
| 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-implicit1.check

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,52 +25,3 @@
2525
|
2626
| import testObjectInstance.instances.zipOption
2727
|
28-
-- [E008] Not Found Error: tests/neg/missing-implicit1.scala:26:16 -----------------------------------------------------
29-
26 | List(1, 2, 3).first // error
30-
| ^^^^^^^^^^^^^^^^^^^
31-
| value first is not a member of List[Int], but could be made available as an extension method.
32-
|
33-
| The following import might fix the problem:
34-
|
35-
| import testObjectInstance.instances.first
36-
|
37-
-- [E008] Not Found Error: tests/neg/missing-implicit1.scala:27:16 -----------------------------------------------------
38-
27 | List(1, 2, 3).second // error
39-
| ^^^^^^^^^^^^^^^^^^^^
40-
| value second is not a member of List[Int], but could be made available as an extension method.
41-
|
42-
| The following import might fix the problem:
43-
|
44-
| import testObjectInstance.instances.listExtension
45-
|
46-
-- [E008] Not Found Error: tests/neg/missing-implicit1.scala:28:17 -----------------------------------------------------
47-
28 | Array(1, 2, 3).first // error, no hint
48-
| ^^^^^^^^^^^^^^^^^^^^
49-
| value first is not a member of Array[Int]
50-
-- Error: tests/neg/missing-implicit1.scala:44:4 -----------------------------------------------------------------------
51-
44 | ff // error
52-
| ^
53-
| no implicit argument of type Zip[Option] was found for parameter xs of method ff
54-
|
55-
| The following import might fix the problem:
56-
|
57-
| import instances.zipOption
58-
|
59-
-- [E008] Not Found Error: tests/neg/missing-implicit1.scala:46:16 -----------------------------------------------------
60-
46 | List(1, 2, 3).traverse(x => Option(x)) // error
61-
| ^^^^^^^^^^^^^^^^^^^^^^
62-
| value traverse is not a member of List[Int], but could be made available as an extension method.
63-
|
64-
| The following import might make progress towards fixing the problem:
65-
|
66-
| import instances.traverseList
67-
|
68-
-- Error: tests/neg/missing-implicit1.scala:50:42 ----------------------------------------------------------------------
69-
50 | List(1, 2, 3).traverse(x => Option(x)) // error
70-
| ^
71-
|no implicit argument of type Zip[Option] was found for an implicit parameter of method traverse in trait Traverse
72-
|
73-
|The following import might fix the problem:
74-
|
75-
| import instances.zipOption
76-
|

0 commit comments

Comments
 (0)