Skip to content

Commit aabcd81

Browse files
committed
Also suggest imports of extension methods
Also suggest direct imports of extension methods that are not members of a given instance.
1 parent 9e72e6c commit aabcd81

File tree

5 files changed

+89
-27
lines changed

5 files changed

+89
-27
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ object ErrorReporting {
157157
|| expected.isRef(defn.ObjectClass)
158158
|| defn.isBottomType(found)
159159
then postScript
160-
else ctx.typer.implicitSuggestionAddendum(ViewProto(found.widen, expected))
160+
else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected))
161161
TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript1)
162162
}
163163

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

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,8 @@ trait Implicits { self: Typer =>
696696
}
697697

698698
/** A list of TermRefs referring to the roots where suggestions for
699-
* imports of givens that might fix a type error are searched.
699+
* imports of givens or extension methods that might fix a type error
700+
* are searched.
700701
*
701702
* These roots are the smallest set of objects and packages that includes
702703
*
@@ -798,6 +799,11 @@ trait Implicits { self: Typer =>
798799
*
799800
* 2. The _head matching_ given instances, that conform to the
800801
* expected type `pt`, ignoring any dependent implicit arguments.
802+
*
803+
* If there are no fully matching given instances under (1), and `pt` is
804+
* a view prototype of a selection of the form `T ?=>? { name: ... }`,
805+
* return instead a list of all possible references to extension methods named
806+
* `name` that are applicable to `T`.
801807
*/
802808
private def importSuggestions(pt: Type)(given ctx: Context): (List[TermRef], List[TermRef]) =
803809
val timer = new Timer()
@@ -806,14 +812,14 @@ trait Implicits { self: Typer =>
806812
/** Test whether the head of a given instance matches the expected type `pt`,
807813
* ignoring any dependent implicit arguments.
808814
*/
809-
def shallowTest(ref: TermRef)(given Context): Boolean =
815+
def shallowTest(ref: TermRef): Boolean =
810816
System.currentTimeMillis < deadLine
811817
&& (ref <:< pt)(given ctx.fresh.setExploreTyperState())
812818

813819
/** Test whether a full given term can be synthesized that matches
814820
* the expected type `pt`.
815821
*/
816-
def deepTest(ref: TermRef)(given Context): Boolean =
822+
def deepTest(ref: TermRef): Boolean =
817823
System.currentTimeMillis < deadLine
818824
&& {
819825
val task = new TimerTask with
@@ -840,14 +846,37 @@ trait Implicits { self: Typer =>
840846
}
841847
end deepTest
842848

849+
/** Optionally, an extension method reference `site.name` that is
850+
* applicable to `argType`.
851+
*/
852+
def extensionMethod(site: TermRef, name: TermName, argType: Type): Option[TermRef] =
853+
site.member(name)
854+
.alternatives
855+
.map(mbr => TermRef(site, mbr.symbol))
856+
.filter(ref =>
857+
ref.symbol.is(Extension)
858+
&& isApplicableMethodRef(ref, argType :: Nil, WildcardType))
859+
.headOption
860+
843861
try
844-
suggestionRoots
862+
val roots = suggestionRoots
845863
.filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol))
846864
// don't suggest things that are imported by default
865+
866+
def extensionImports = pt match
867+
case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) =>
868+
roots.flatMap(extensionMethod(_, name, argType))
869+
case _ =>
870+
Nil
871+
872+
roots
847873
.flatMap(_.implicitMembers.filter(shallowTest))
848874
// filter whether the head of the implicit can match
849875
.partition(deepTest)
850876
// partition into full matches and head matches
877+
match
878+
case (Nil, partials) => (extensionImports, partials)
879+
case givenImports => givenImports
851880
catch
852881
case ex: Throwable =>
853882
if ctx.settings.Ydebug.value then
@@ -862,7 +891,7 @@ trait Implicits { self: Typer =>
862891
* The addendum suggests given imports that might fix the problem.
863892
* If there's nothing to suggest, an empty string is returned.
864893
*/
865-
override def implicitSuggestionAddendum(pt: Type)(given ctx: Context): String =
894+
override def importSuggestionAddendum(pt: Type)(given ctx: Context): String =
866895
val (fullMatches, headMatches) =
867896
importSuggestions(pt)(given ctx.fresh.setExploreTyperState())
868897
implicits.println(i"suggestions for $pt in ${ctx.owner} = ($fullMatches%, %, $headMatches%, %)")
@@ -873,12 +902,14 @@ trait Implicits { self: Typer =>
873902
s" import ${ctx.printer.toTextRef(ref).show}"
874903
val suggestions = suggestedRefs
875904
.zip(suggestedRefs.map(importString))
876-
.filter(_._2.contains('.'))
877-
.sortWith { (rs1, rs2) => // sort by specificity first, alphabetically second
878-
val diff = compare(rs1._1, rs2._1)
879-
diff > 0 || diff == 0 && rs1._2 < rs2._2
905+
.filter((ref, str) => str.contains('.'))
906+
.sortWith { (x, y) =>
907+
// sort by specificity first, alphabetically second
908+
val ((ref1, str1), (ref2, str2)) = (x, y)
909+
val diff = compare(ref1, ref2)
910+
diff > 0 || diff == 0 && str1 < str2
880911
}
881-
.map(_._2)
912+
.map((ref, str) => str)
882913
.distinct // TermRefs might be different but generate the same strings
883914
if suggestions.isEmpty then ""
884915
else
@@ -891,7 +922,7 @@ trait Implicits { self: Typer =>
891922
|
892923
|$suggestions%\n%
893924
"""
894-
end implicitSuggestionAddendum
925+
end importSuggestionAddendum
895926

896927
/** Handlers to synthesize implicits for special types */
897928
type SpecialHandler = (Type, Span) => Context => Tree
@@ -1452,7 +1483,7 @@ trait Implicits { self: Typer =>
14521483
// example where searching for a nested type causes an infinite loop.
14531484
""
14541485

1455-
def suggestedImports = implicitSuggestionAddendum(pt)
1486+
def suggestedImports = importSuggestionAddendum(pt)
14561487
if normalImports.isEmpty then suggestedImports else normalImports
14571488
end hiddenImplicitsAddendum
14581489

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ trait TypeAssigner {
283283
|If you do not want that, insert a `;` or empty line in front
284284
|or drop any spaces behind the operator."""
285285
else
286-
var add = implicitSuggestionAddendum(
286+
var add = importSuggestionAddendum(
287287
ViewProto(qualType.widen,
288288
SelectionProto(name, WildcardType, NoViewsAllowed, privateOK = false)))
289289
if add.isEmpty then ""
@@ -293,7 +293,7 @@ trait TypeAssigner {
293293
}
294294
}
295295

296-
def implicitSuggestionAddendum(pt: Type)(given Context): String = ""
296+
def importSuggestionAddendum(pt: Type)(given Context): String = ""
297297

298298
/** The type of the selection in `tree`, where `qual1` is the typed qualifier part.
299299
* The selection type is additionally checked for accessibility.

tests/neg/missing-implicit1.check

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,72 @@
1-
-- Error: tests/neg/missing-implicit1.scala:14:4 -----------------------------------------------------------------------
2-
14 | ff // error
1+
-- Error: tests/neg/missing-implicit1.scala:17:4 -----------------------------------------------------------------------
2+
17 | ff // error
33
| ^
44
|no implicit argument of type testObjectInstance.Zip[Option] was found for parameter xs of method ff in object testObjectInstance
55
|
66
|The following import might fix the problem:
77
|
88
| import testObjectInstance.instances.zipOption
99
|
10-
-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:16:16 ----------------------------------------------
11-
16 | List(1, 2, 3).traverse(x => Option(x)) // error
10+
-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:19:16 ----------------------------------------------
11+
19 | List(1, 2, 3).traverse(x => Option(x)) // error
1212
| ^^^^^^^^^^^^^^^^^^^^^^
1313
| value traverse is not a member of List[Int], but could be made available as an extension method.
1414
|
1515
| The following import might make progress towards fixing the problem:
1616
|
1717
| import testObjectInstance.instances.traverseList
1818
|
19-
-- Error: tests/neg/missing-implicit1.scala:20:42 ----------------------------------------------------------------------
20-
20 | List(1, 2, 3).traverse(x => Option(x)) // error
19+
-- Error: tests/neg/missing-implicit1.scala:23:42 ----------------------------------------------------------------------
20+
23 | List(1, 2, 3).traverse(x => Option(x)) // error
2121
| ^
2222
|no implicit argument of type testObjectInstance.Zip[Option] was found for parameter evidence$1 of method traverse in trait Traverse
2323
|
2424
|The following import might fix the problem:
2525
|
2626
| import testObjectInstance.instances.zipOption
2727
|
28-
-- Error: tests/neg/missing-implicit1.scala:36:4 -----------------------------------------------------------------------
29-
36 | ff // error
28+
-- [E008] Member 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] Member 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] Member 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
3052
| ^
3153
| no implicit argument of type Zip[Option] was found for parameter xs of method ff
3254
|
3355
| The following import might fix the problem:
3456
|
3557
| import instances.zipOption
3658
|
37-
-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:38:16 ----------------------------------------------
38-
38 | List(1, 2, 3).traverse(x => Option(x)) // error
59+
-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:46:16 ----------------------------------------------
60+
46 | List(1, 2, 3).traverse(x => Option(x)) // error
3961
| ^^^^^^^^^^^^^^^^^^^^^^
4062
| value traverse is not a member of List[Int], but could be made available as an extension method.
4163
|
4264
| The following import might make progress towards fixing the problem:
4365
|
4466
| import instances.traverseList
4567
|
46-
-- Error: tests/neg/missing-implicit1.scala:42:42 ----------------------------------------------------------------------
47-
42 | List(1, 2, 3).traverse(x => Option(x)) // error
68+
-- Error: tests/neg/missing-implicit1.scala:50:42 ----------------------------------------------------------------------
69+
50 | List(1, 2, 3).traverse(x => Option(x)) // error
4870
| ^
4971
|no implicit argument of type Zip[Option] was found for parameter evidence$2 of method traverse in trait Traverse
5072
|

tests/neg/missing-implicit1.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ object testObjectInstance with
77
object instances {
88
given zipOption: Zip[Option] = ???
99
given traverseList: Traverse[List] = ???
10+
given listExtension: [T](xs: List[T]) extended with
11+
def second: T = xs.tail.head
12+
def [T](xs: List[T]) first: T = xs.head
1013
}
1114

1215
def ff(given xs: Zip[Option]) = ???
@@ -20,6 +23,11 @@ object testObjectInstance with
2023
List(1, 2, 3).traverse(x => Option(x)) // error
2124
}
2225

26+
List(1, 2, 3).first // error
27+
List(1, 2, 3).second // error
28+
Array(1, 2, 3).first // error, no hint
29+
end testObjectInstance
30+
2331
def testLocalInstance =
2432
trait Zip[F[_]]
2533
trait Traverse[F[_]] {
@@ -41,3 +49,4 @@ def testLocalInstance =
4149
import instances.traverseList
4250
List(1, 2, 3).traverse(x => Option(x)) // error
4351
}
52+
end testLocalInstance

0 commit comments

Comments
 (0)