Skip to content

Commit 5618836

Browse files
committed
Fix #10901: Explain why attempted extension methods could not be typed
We now also show with each tried extension method call that failed type checking the first error message that indicates the failure.
1 parent 537aee7 commit 5618836

File tree

12 files changed

+174
-29
lines changed

12 files changed

+174
-29
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ object Decorators {
5656
termName(chars, 0, len)
5757
case name: TypeName => s.concat(name.toTermName)
5858
case _ => termName(s.concat(name.toString))
59+
60+
def indented(width: Int): String =
61+
val padding = " " * width
62+
padding + s.replace("\n", "\n" + padding)
5963
end extension
6064

6165
/** Implements a findSymbol method on iterators of Symbols that

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
168168
CannotExtendJavaEnumID,
169169
InvalidReferenceInImplicitNotFoundAnnotationID,
170170
TraitMayNotDefineNativeMethodID,
171-
JavaEnumParentArgsID
171+
JavaEnumParentArgsID,
172+
NotAnExtensionMethodID
172173

173174
def errorNumber = ordinal - 2
174175
}

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2402,6 +2402,11 @@ import transform.SymUtils._
24022402
|""".stripMargin
24032403
}
24042404

2405+
class NotAnExtensionMethod(methodRef: untpd.Tree)(using Context)
2406+
extends TypeMsg(NotAnExtensionMethodID):
2407+
def msg = em"not an extension method: $methodRef"
2408+
def explain = ""
2409+
24052410
class NoExtensionMethodAllowed(mdef: untpd.DefDef)(using Context)
24062411
extends SyntaxMsg(NoExtensionMethodAllowedID) {
24072412
def msg = em"No extension method allowed here, since collective parameters are given"

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,8 +2163,7 @@ trait Applications extends Compatibility {
21632163
case tree @ Select(qual, nme.apply) => tree.symbol.is(ExtensionMethod) || isExtension(qual)
21642164
case tree => tree.symbol.is(ExtensionMethod)
21652165
}
2166-
if (!isExtension(app))
2167-
report.error(em"not an extension method: $methodRef", receiver.srcPos)
2166+
if !isExtension(app) then report.error(NotAnExtensionMethod(methodRef), receiver.srcPos)
21682167
app
21692168
}
21702169

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,27 +144,35 @@ object ErrorReporting {
144144
def selectErrorAddendum
145145
(tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String)
146146
(using Context): String =
147-
val attempts = mutable.ListBuffer[Tree]()
147+
148+
val attempts = mutable.ListBuffer[(Tree, FailedExtension)]()
148149
val nested = mutable.ListBuffer[NestedFailure]()
149-
qual1.getAttachment(Typer.HiddenSearchFailure) match
150-
case Some(failures) =>
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 _ =>
150+
for
151+
failures <- qual1.getAttachment(Typer.HiddenSearchFailure)
152+
failure <- failures
153+
do
154+
failure.reason match
155+
case fail: NestedFailure => nested += fail
156+
case fail: FailedExtension => attempts += ((failure.tree, fail))
157+
case _ =>
157158
if qualType.derivesFrom(defn.DynamicClass) then
158159
"\npossible cause: maybe a wrong Dynamic method signature?"
159160
else if attempts.nonEmpty then
160-
val attemptStrings = attempts.toList.map(_.showIndented(4)).distinct
161+
val attemptStrings =
162+
attempts.toList
163+
.map((tree, whyFailed) => (tree.showIndented(4), whyFailed))
164+
.distinctBy(_._1)
165+
.map((treeStr, whyFailed) =>
166+
i"""
167+
| $treeStr failed with
168+
|
169+
|${whyFailed.whyFailed.message.indented(8)}""")
161170
val extMethods =
162171
if attemptStrings.length > 1 then "Extension methods were"
163172
else "An extension method was"
164173
i""".
165174
|$extMethods tried, but could not be fully constructed:
166-
|
167-
| $attemptStrings%\nor\n %"""
175+
|$attemptStrings%\n%"""
168176
else if nested.nonEmpty then
169177
i""".
170178
|Extension methods were tried, but the search failed with:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ object Implicits:
514514
}
515515

516516
/** A search failure type for attempted ill-typed extension method calls */
517-
class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType:
517+
class FailedExtension(extApp: Tree, val expectedType: Type, val whyFailed: Message) extends SearchFailureType:
518518
def argument = EmptyTree
519519
def explanation(using Context) = em"$extApp does not $qualify"
520520

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3513,12 +3513,14 @@ class Typer extends Namer
35133513
val nestedCtx = ctx.fresh.setNewTyperState()
35143514
val app = tryExtension(using nestedCtx)
35153515
if !app.isEmpty then
3516-
if !nestedCtx.reporter.hasErrors then
3517-
nestedCtx.typerState.commit()
3518-
return ExtMethodApply(app)
3519-
else
3520-
rememberSearchFailure(tree,
3521-
SearchFailure(app.withType(FailedExtension(app, pt))))
3516+
nestedCtx.reporter.allErrors
3517+
.filterNot(_.msg.isInstanceOf[NotAnExtensionMethod]) match
3518+
case Nil =>
3519+
nestedCtx.typerState.commit()
3520+
return ExtMethodApply(app)
3521+
case err :: _ =>
3522+
rememberSearchFailure(tree,
3523+
SearchFailure(app.withType(FailedExtension(app, pt, err.msg))))
35223524
catch case ex: TypeError =>
35233525
rememberSearchFailure(tree,
35243526
SearchFailure(tree.withType(NestedFailure(ex.toMessage, pt))))

tests/neg/enum-values.check

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
| meaning a values array is not defined.
77
| An extension method was tried, but could not be fully constructed:
88
|
9-
| example.Extensions.values(Tag)
9+
| example.Extensions.values(Tag) failed with
10+
|
11+
| Found: example.Tag.type
12+
| Required: Nothing
1013
-- [E008] Not Found Error: tests/neg/enum-values.scala:33:50 -----------------------------------------------------------
1114
33 | val listlikes: Array[ListLike[?]] = ListLike.values // error
1215
| ^^^^^^^^^^^^^^^
@@ -15,7 +18,10 @@
1518
| meaning a values array is not defined.
1619
| An extension method was tried, but could not be fully constructed:
1720
|
18-
| example.Extensions.values(ListLike)
21+
| example.Extensions.values(ListLike) failed with
22+
|
23+
| Found: example.ListLike.type
24+
| Required: Nothing
1925
-- [E008] Not Found Error: tests/neg/enum-values.scala:34:52 -----------------------------------------------------------
2026
34 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error
2127
| ^^^^^^^^^^^^^^^^^
@@ -24,7 +30,10 @@
2430
| meaning a values array is not defined.
2531
| An extension method was tried, but could not be fully constructed:
2632
|
27-
| example.Extensions.values(TypeCtorsK)
33+
| example.Extensions.values(TypeCtorsK) failed with
34+
|
35+
| Found: example.TypeCtorsK.type
36+
| Required: Nothing
2837
-- [E008] Not Found Error: tests/neg/enum-values.scala:36:6 ------------------------------------------------------------
2938
36 | Tag.valueOf("Int") // error
3039
| ^^^^^^^^^^^
@@ -54,7 +63,10 @@
5463
| value values is not a member of object example.NotAnEnum.
5564
| An extension method was tried, but could not be fully constructed:
5665
|
57-
| example.Extensions.values(NotAnEnum)
66+
| example.Extensions.values(NotAnEnum) failed with
67+
|
68+
| Found: example.NotAnEnum.type
69+
| Required: Nothing
5870
-- [E008] Not Found Error: tests/neg/enum-values.scala:41:12 -----------------------------------------------------------
5971
41 | NotAnEnum.valueOf("Foo") // error
6072
| ^^^^^^^^^^^^^^^^^

tests/neg/i10901.check

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
-- [E008] Not Found Error: tests/neg/i10901.scala:45:38 ----------------------------------------------------------------
2+
45 | val pos1: Point2D[Int,Double] = x º y // error
3+
| ^^^
4+
| value º is not a member of object BugExp4Point2D.IntT.
5+
| An extension method was tried, but could not be fully constructed:
6+
|
7+
| º(x) failed with
8+
|
9+
| Ambiguous overload. The overloaded alternatives of method º in object dsl with types
10+
| [T1, T2]
11+
| (x: BugExp4Point2D.ColumnType[T1])
12+
| (y: BugExp4Point2D.ColumnType[T2])
13+
| (implicit evidence$7: Numeric[T1], evidence$8: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
14+
| [T1, T2]
15+
| (x: T1)
16+
| (y: BugExp4Point2D.ColumnType[T2])
17+
| (implicit evidence$5: Numeric[T1], evidence$6: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
18+
| both match arguments ((x : BugExp4Point2D.IntT.type))
19+
-- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ----------------------------------------------------------------
20+
48 | val pos4: Point2D[Int,Double] = x º 201.1 // error
21+
| ^^^
22+
|value º is not a member of object BugExp4Point2D.IntT.
23+
|An extension method was tried, but could not be fully constructed:
24+
|
25+
| º(x) failed with
26+
|
27+
| Ambiguous overload. The overloaded alternatives of method º in object dsl with types
28+
| [T1, T2]
29+
| (x: BugExp4Point2D.ColumnType[T1])
30+
| (y: T2)(implicit evidence$9: Numeric[T1], evidence$10: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
31+
| [T1, T2](x: T1)(y: T2)(implicit evidence$3: Numeric[T1], evidence$4: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
32+
| both match arguments ((x : BugExp4Point2D.IntT.type))
33+
-- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ----------------------------------------------------------------
34+
62 | val y = "abc".foo // error
35+
| ^^^^^^^^^
36+
| value foo is not a member of String.
37+
| An extension method was tried, but could not be fully constructed:
38+
|
39+
| Test.foo("abc")(/* missing */summon[C]) failed with
40+
|
41+
| no implicit argument of type C was found for parameter x$1 of method foo in object Test

tests/neg/i10901.scala

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import scala.annotation.targetName
2+
3+
object BugExp4Point2D {
4+
5+
sealed trait ColumnType[T]
6+
case object DoubleT extends ColumnType[Double]
7+
case object IntT extends ColumnType[Int]
8+
9+
object dsl {
10+
11+
12+
extension [T1:Numeric, T2:Numeric](x: T1)
13+
14+
// N - N
15+
@targetName("point2DConstant")
16+
def º(y: T2): Point2D[T1,T2] = ???
17+
18+
19+
// N - C
20+
@targetName("point2DConstantData")
21+
def º(y: ColumnType[T2]): Point2D[T1,T2] = ???
22+
23+
24+
25+
extension [T1:Numeric, T2:Numeric](x: ColumnType[T1])
26+
// C - C
27+
@targetName("point2DData")
28+
def º(y: ColumnType[T2]): Point2D[T1,T2] = ???
29+
30+
// C - N
31+
@targetName("point2DDataConstant")
32+
def º(y: T2): Point2D[T1,T2] = ???
33+
34+
35+
}
36+
37+
case class Point2D[T1:Numeric, T2:Numeric](x:T1, y:T2)
38+
39+
import dsl._
40+
41+
def main(args: Array[String]): Unit = {
42+
val x = IntT
43+
val y = DoubleT
44+
45+
val pos1: Point2D[Int,Double] = x º y // error
46+
val pos2: Point2D[Int,Double] = 100 º 200.1 // ok
47+
val pos3: Point2D[Int,Double] = 101 º y // ok
48+
val pos4: Point2D[Int,Double] = x º 201.1 // error
49+
50+
}
51+
}
52+
53+
class C
54+
55+
object Container:
56+
given C with {}
57+
58+
object Test:
59+
extension (x: String)(using C)
60+
def foo: String = x
61+
62+
val y = "abc".foo // error
63+

0 commit comments

Comments
 (0)