Skip to content

Commit aa97cba

Browse files
committed
Fix scala#10810: Better error diagnostics for extension methods
We gave misleading diagnostic before in the case where no extension method was found since a TypeError was thrown.
1 parent 46290f9 commit aa97cba

File tree

5 files changed

+64
-21
lines changed

5 files changed

+64
-21
lines changed

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import util.SrcPos
1313
import config.Feature
1414
import java.util.regex.Matcher.quoteReplacement
1515
import reporting._
16+
import collection.mutable
1617

1718
import scala.util.matching.Regex
1819

@@ -143,23 +144,32 @@ object ErrorReporting {
143144
def selectErrorAddendum
144145
(tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String)
145146
(using Context): String =
146-
val attempts: List[Tree] = qual1.getAttachment(Typer.HiddenSearchFailure) match
147+
val attempts = mutable.ListBuffer[Tree]()
148+
val nested = mutable.ListBuffer[NestedFailure]()
149+
qual1.getAttachment(Typer.HiddenSearchFailure) match
147150
case Some(failures) =>
148-
for failure <- failures
149-
if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits]
150-
yield failure.tree
151-
case _ => Nil
151+
for failure <- failures do
152+
failure.reason match
153+
case fail: NestedFailure => nested += fail
154+
case fail: Implicits.NoMatchingImplicits => // do nothing
155+
case _ => attempts += failure.tree
156+
case _ =>
152157
if qualType.derivesFrom(defn.DynamicClass) then
153158
"\npossible cause: maybe a wrong Dynamic method signature?"
154159
else if attempts.nonEmpty then
155-
val attemptStrings = attempts.map(_.showIndented(4)).distinct
160+
val attemptStrings = attempts.toList.map(_.showIndented(4)).distinct
156161
val extMethods =
157162
if attemptStrings.length > 1 then "Extension methods were"
158163
else "An extension method was"
159164
i""".
160165
|$extMethods tried, but could not be fully constructed:
161166
|
162167
| $attemptStrings%\nor\n %"""
168+
else if nested.nonEmpty then
169+
i""".
170+
|Extension methods were tried, but the search failed with:
171+
|
172+
| ${nested.head.explanation}"""
163173
else if tree.hasAttachment(desugar.MultiLineInfix) then
164174
i""".
165175
|Note that `${tree.name}` is treated as an infix operator in Scala 3.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,19 @@ object Implicits:
513513
em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify"
514514
}
515515

516+
/** A search failure type for attempted ill-typed extension method calls */
516517
class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType:
517518
def argument = EmptyTree
518519
def explanation(using Context) = em"$extApp does not $qualify"
520+
521+
/** A search failure type for aborted searches of extension methods, typically
522+
* because of a cyclic reference or similar.
523+
*/
524+
class NestedFailure(_msg: Message, val expectedType: Type) extends SearchFailureType:
525+
def argument = EmptyTree
526+
override def msg(using Context) = _msg
527+
def explanation(using Context) = msg.toString
528+
519529
end Implicits
520530

521531
import Implicits._

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

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3496,24 +3496,29 @@ class Typer extends Namer
34963496
// try an extension method in scope
34973497
pt match {
34983498
case selProto @ SelectionProto(selName: TermName, mbrType, _, _) =>
3499+
34993500
def tryExtension(using Context): Tree =
3500-
try
3501-
findRef(selName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match
3501+
findRef(selName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match
3502+
case ref: TermRef =>
3503+
extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType)
3504+
case _ => findRef(selProto.extensionName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match
35023505
case ref: TermRef =>
35033506
extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType)
3504-
case _ => findRef(selProto.extensionName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match
3505-
case ref: TermRef =>
3506-
extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType)
3507-
case _ => EmptyTree
3508-
catch case ex: TypeError => errorTree(tree, ex, tree.srcPos)
3509-
val nestedCtx = ctx.fresh.setNewTyperState()
3510-
val app = tryExtension(using nestedCtx)
3511-
if (!app.isEmpty && !nestedCtx.reporter.hasErrors) {
3512-
nestedCtx.typerState.commit()
3513-
return ExtMethodApply(app)
3514-
}
3515-
else if !app.isEmpty then
3516-
rememberSearchFailure(tree, SearchFailure(app.withType(FailedExtension(app, pt))))
3507+
case _ => EmptyTree
3508+
3509+
try
3510+
val nestedCtx = ctx.fresh.setNewTyperState()
3511+
val app = tryExtension(using nestedCtx)
3512+
if !app.isEmpty then
3513+
if !nestedCtx.reporter.hasErrors then
3514+
nestedCtx.typerState.commit()
3515+
return ExtMethodApply(app)
3516+
else
3517+
rememberSearchFailure(tree,
3518+
SearchFailure(app.withType(FailedExtension(app, pt))))
3519+
catch case ex: TypeError =>
3520+
rememberSearchFailure(tree,
3521+
SearchFailure(tree.withType(NestedFailure(ex.toMessage, pt))))
35173522
case _ =>
35183523
}
35193524

tests/neg/i10810.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E008] Not Found Error: tests/neg/i10810.scala:10:16 ----------------------------------------------------------------
2+
10 | def x = b.a.x // error
3+
| ^^^^^
4+
| value x is not a member of A.
5+
| Extension methods were tried, but the search failed with:
6+
|
7+
| Overloaded or recursive method x needs return type

tests/neg/i10810.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
final case class A()
2+
final case class B(a:A)
3+
4+
object Test:
5+
6+
extension(a:A)
7+
def x = 5
8+
9+
extension(b:B)
10+
def x = b.a.x // error
11+

0 commit comments

Comments
 (0)