Skip to content

Commit 255a538

Browse files
authored
Merge pull request #7862 from dotty-staging/add-suggest-givens
Make suggestions of missing implicits imports on type errors
2 parents 4b6d693 + 6f94a83 commit 255a538

24 files changed

+659
-95
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ import scala.util.control.NonFatal
3131
/** A compiler run. Exports various methods to compile source files */
3232
class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo {
3333

34+
/** If this variable is set to `true`, some core typer operations will
35+
* return immediately. Currently these early abort operations are
36+
* `Typer.typed` and `Implicits.typedImplicit`.
37+
*/
38+
@volatile var isCancelled = false
39+
3440
/** Produces the following contexts, from outermost to innermost
3541
*
3642
* bootStrap: A context with next available runId and a scope consisting of

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ class Definitions {
210210
}
211211
@tu lazy val ScalaPackageObject: Symbol = ctx.requiredModule("scala.package")
212212
@tu lazy val JavaPackageVal: TermSymbol = ctx.requiredPackage(nme.java)
213+
@tu lazy val JavaPackageClass: ClassSymbol = JavaPackageVal.moduleClass.asClass
213214
@tu lazy val JavaLangPackageVal: TermSymbol = ctx.requiredPackage(jnme.JavaLang)
215+
@tu lazy val JavaLangPackageClass: ClassSymbol = JavaLangPackageVal.moduleClass.asClass
214216

215217
// fundamental modules
216218
@tu lazy val SysPackage : Symbol = ctx.requiredModule("scala.sys.package")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2205,7 +2205,7 @@ object SymDenotations {
22052205

22062206
if (symbol `eq` defn.ScalaPackageClass) {
22072207
val denots = super.computeNPMembersNamed(name)
2208-
if (denots.exists) denots
2208+
if (denots.exists || name == nme.CONSTRUCTOR) denots
22092209
else recur(packageObjs, NoDenotation)
22102210
}
22112211
else recur(packageObjs, NoDenotation)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ object CyclicReference {
153153
def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = {
154154
val ex = new CyclicReference(denot)
155155
if (!(ctx.mode is Mode.CheckCyclic)) {
156-
cyclicErrors.println(ex.getMessage)
156+
cyclicErrors.println(s"Cyclic reference involving $denot")
157157
for (elem <- ex.getStackTrace take 200)
158158
cyclicErrors.println(elem.toString)
159159
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
278278
}
279279

280280
/** The string representation of this type used as a prefix */
281-
protected def toTextRef(tp: SingletonType): Text = controlled {
281+
def toTextRef(tp: SingletonType): Text = controlled {
282282
tp match {
283283
case tp: TermRef =>
284284
toTextPrefix(tp.prefix) ~ selectionString(tp)
@@ -438,6 +438,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
438438
(kindString(sym) ~~ {
439439
if (sym.isAnonymousClass) toTextParents(sym.info.parents) ~~ "{...}"
440440
else if (hasMeaninglessName(sym) && !printDebug) simpleNameString(sym.owner) + idString(sym)
441+
else if sym.is(Package) then fullNameString(sym)
441442
else nameString(sym)
442443
}).close
443444

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package printing
44

55
import core._
66
import Texts._, ast.Trees._
7-
import Types.Type, Symbols.Symbol, Scopes.Scope, Constants.Constant,
7+
import Types.{Type, SingletonType}, Symbols.Symbol, Scopes.Scope, Constants.Constant,
88
Names.Name, Denotations._, Annotations.Annotation
99
import typer.Implicits.SearchResult
1010
import util.SourcePosition
@@ -97,6 +97,9 @@ abstract class Printer {
9797
*/
9898
def toText(sym: Symbol): Text
9999

100+
/** Textual representation of singeton type reference */
101+
def toTextRef(tp: SingletonType): Text
102+
100103
/** Textual representation of symbol's declaration */
101104
def dclText(sym: Symbol): Text
102105

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
900900
def name =
901901
if (printDebug)
902902
nameString(sym)
903+
else if sym.is(Package) then
904+
fullNameString(sym)
903905
else if (sym.is(ModuleClass) && sym.isPackageObject && sym.name.stripModuleClassSuffix == tpnme.PACKAGE)
904906
nameString(sym.owner.name)
905907
else if (sym.is(ModuleClass))

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import diagnostic.messages._
1515
import diagnostic._
1616
import ast.{tpd, Trees}
1717
import Message._
18+
import core.Decorators._
1819

1920
import java.lang.System.currentTimeMillis
2021
import java.io.{ BufferedReader, PrintWriter }

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

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -343,33 +343,35 @@ object messages {
343343
}
344344

345345
// Get closest match in `site`
346-
val closest =
346+
def closest: List[String] =
347347
decls
348-
.map { case (n, sym) => (n, distance(n, name.show), sym) }
349-
.collect { case (n, dist, sym) if dist <= maxDist => (n, dist, sym) }
348+
.map { (n, sym) => (n, distance(n, name.show), sym) }
349+
.collect {
350+
case (n, dist, sym)
351+
if dist <= maxDist && dist < (name.toString.length min n.length) =>
352+
(n, dist, sym)
353+
}
350354
.groupBy(_._2).toList
351355
.sortBy(_._1)
352356
.headOption.map(_._2).getOrElse(Nil)
353357
.map(incorrectChars).toList
354358
.sortBy(_._3)
355-
.take(1).map { case (n, sym, _) => (n, sym) }
356-
357-
val siteName = site match {
358-
case site: NamedType => site.name.show
359-
case site => i"$site"
360-
}
361-
362-
val closeMember = closest match {
363-
case (n, sym) :: Nil =>
364-
s" - did you mean $siteName.$n?"
365-
case Nil => ""
366-
case _ => assert(
367-
false,
368-
"Could not single out one distinct member to match on input with"
369-
)
370-
}
359+
.map(_._1)
360+
// [Martin] Note: I have no idea what this does. This shows the
361+
// pitfalls of not naming things, functional or not.
362+
363+
val finalAddendum =
364+
if addendum.nonEmpty then addendum
365+
else closest match {
366+
case n :: _ =>
367+
val siteName = site match
368+
case site: NamedType => site.name.show
369+
case site => i"$site"
370+
s" - did you mean $siteName.$n?"
371+
case Nil => ""
372+
}
371373

372-
ex"$selected $name is not a member of ${site.widen}$closeMember$addendum"
374+
ex"$selected $name is not a member of ${site.widen}$finalAddendum"
373375
}
374376

375377
val explanation: String = ""

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,15 @@ object ErrorReporting {
150150
val expected1 = reported(expected)
151151
val (found2, expected2) =
152152
if (found1 frozen_<:< expected1) (found, expected) else (found1, expected1)
153-
TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript)
153+
val postScript1 =
154+
if !postScript.isEmpty
155+
|| expected.isRef(defn.AnyClass)
156+
|| expected.isRef(defn.AnyValClass)
157+
|| expected.isRef(defn.ObjectClass)
158+
|| defn.isBottomType(found)
159+
then postScript
160+
else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected))
161+
TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript1)
154162
}
155163

156164
/** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing

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

Lines changed: 70 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Flags._
1414
import TypeErasure.{erasure, hasStableErasure}
1515
import Mode.ImplicitsEnabled
1616
import NameOps._
17-
import NameKinds.LazyImplicitName
17+
import NameKinds.{LazyImplicitName, EvidenceParamName}
1818
import Symbols._
1919
import Denotations._
2020
import Types._
@@ -67,6 +67,14 @@ object Implicits {
6767
final val Extension = 4
6868
}
6969

70+
/** If `expected` is a selection prototype, does `tp` have an extension
71+
* method with the selecting name? False otherwise.
72+
*/
73+
def hasExtMethod(tp: Type, expected: Type)(given Context) = expected match
74+
case SelectionProto(name, _, _, _) =>
75+
tp.memberBasedOnFlags(name, required = ExtensionMethod).exists
76+
case _ => false
77+
7078
/** A common base class of contextual implicits and of-type implicits which
7179
* represents a set of references to implicit definitions.
7280
*/
@@ -147,11 +155,7 @@ object Implicits {
147155
val isImplicitConversion = tpw.derivesFrom(defn.ConversionClass)
148156
// An implementation of <:< counts as a view
149157
val isConforms = tpw.derivesFrom(defn.SubTypeClass)
150-
val hasExtensions = resType match {
151-
case SelectionProto(name, _, _, _) =>
152-
tpw.memberBasedOnFlags(name, required = ExtensionMethod).exists
153-
case _ => false
154-
}
158+
val hasExtensions = hasExtMethod(tpw, resType)
155159
val conversionKind =
156160
if (isFunctionInS2 || isImplicitConversion || isConforms) Candidate.Conversion
157161
else Candidate.None
@@ -1215,32 +1219,37 @@ trait Implicits { self: Typer =>
12151219
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
12161220
pt.widenExpr.argInfos))
12171221

1218-
def hiddenImplicitsAddendum: String = arg.tpe match {
1219-
case fail: SearchFailureType =>
1220-
1221-
def hiddenImplicitNote(s: SearchSuccess) =
1222-
em"\n\nNote: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`."
1222+
def hiddenImplicitsAddendum: String =
1223+
1224+
def hiddenImplicitNote(s: SearchSuccess) =
1225+
em"\n\nNote: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`."
1226+
1227+
def FindHiddenImplicitsCtx(ctx: Context): Context =
1228+
if (ctx == NoContext) ctx
1229+
else ctx.freshOver(FindHiddenImplicitsCtx(ctx.outer)).addMode(Mode.FindHiddenImplicits)
1230+
1231+
val normalImports = arg.tpe match
1232+
case fail: SearchFailureType =>
1233+
if (fail.expectedType eq pt) || isFullyDefined(fail.expectedType, ForceDegree.none) then
1234+
inferImplicit(fail.expectedType, fail.argument, arg.span)(
1235+
FindHiddenImplicitsCtx(ctx)) match {
1236+
case s: SearchSuccess => hiddenImplicitNote(s)
1237+
case f: SearchFailure =>
1238+
f.reason match {
1239+
case ambi: AmbiguousImplicits => hiddenImplicitNote(ambi.alt1)
1240+
case r => ""
1241+
}
1242+
}
1243+
else
1244+
// It's unsafe to search for parts of the expected type if they are not fully defined,
1245+
// since these come with nested contexts that are lost at this point. See #7249 for an
1246+
// example where searching for a nested type causes an infinite loop.
1247+
""
12231248

1224-
def FindHiddenImplicitsCtx(ctx: Context): Context =
1225-
if (ctx == NoContext) ctx
1226-
else ctx.freshOver(FindHiddenImplicitsCtx(ctx.outer)).addMode(Mode.FindHiddenImplicits)
1249+
def suggestedImports = importSuggestionAddendum(pt)
1250+
if normalImports.isEmpty then suggestedImports else normalImports
1251+
end hiddenImplicitsAddendum
12271252

1228-
if (fail.expectedType eq pt) || isFullyDefined(fail.expectedType, ForceDegree.none) then
1229-
inferImplicit(fail.expectedType, fail.argument, arg.span)(
1230-
FindHiddenImplicitsCtx(ctx)) match {
1231-
case s: SearchSuccess => hiddenImplicitNote(s)
1232-
case f: SearchFailure =>
1233-
f.reason match {
1234-
case ambi: AmbiguousImplicits => hiddenImplicitNote(ambi.alt1)
1235-
case r => ""
1236-
}
1237-
}
1238-
else
1239-
// It's unsafe to search for parts of the expected type if they are not fully defined,
1240-
// since these come with nested contexts that are lost at this point. See #7249 for an
1241-
// example where searching for a nested type causes an infinite loop.
1242-
""
1243-
}
12441253
msg(userDefined.getOrElse(
12451254
em"no implicit argument of type $pt was found${location("for")}"))() ++
12461255
hiddenImplicitsAddendum
@@ -1256,7 +1265,8 @@ trait Implicits { self: Typer =>
12561265
def addendum = if (qt1 eq qt) "" else (i"\nwhich is an alias of: $qt1")
12571266
em"parameter of ${qual.tpe.widen}$addendum"
12581267
case _ =>
1259-
em"parameter ${paramName} of $methodStr"
1268+
em"${ if paramName.is(EvidenceParamName) then "an implicit parameter"
1269+
else s"parameter $paramName" } of $methodStr"
12601270
}
12611271

12621272
private def strictEquality(implicit ctx: Context): Boolean =
@@ -1352,32 +1362,10 @@ trait Implicits { self: Typer =>
13521362
ctx.searchHistory.emitDictionary(span, result)
13531363
}
13541364

1355-
/** An implicit search; parameters as in `inferImplicit` */
1356-
class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(implicit ctx: Context) {
1357-
assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType],
1358-
em"found: $argument: ${argument.tpe}, expected: $pt")
1359-
1360-
private def nestedContext() =
1361-
ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled)
1362-
1363-
private def implicitProto(resultType: Type, f: Type => Type) =
1364-
if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType))
1365-
// Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though.
1366-
1367-
private def isCoherent = pt.isRef(defn.EqlClass)
1368-
1369-
/** The expected type for the searched implicit */
1370-
@threadUnsafe lazy val fullProto: Type = implicitProto(pt, identity)
1371-
1372-
/** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */
1373-
val wildProto: Type = implicitProto(pt, wildApprox(_))
1374-
1375-
val isNot: Boolean = wildProto.classSymbol == defn.NotClass
1376-
1377-
//println(i"search implicits $pt / ${eligible.map(_.ref)}")
1378-
1379-
/** Try to typecheck an implicit reference */
1380-
def typedImplicit(cand: Candidate, contextual: Boolean)(implicit ctx: Context): SearchResult = trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) {
1365+
/** Try to typecheck an implicit reference */
1366+
def typedImplicit(cand: Candidate, pt: Type, argument: Tree, span: Span)(implicit ctx: Context): SearchResult = trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) {
1367+
if ctx.run.isCancelled then NoMatchingImplicitsFailure
1368+
else
13811369
record("typedImplicit")
13821370
val ref = cand.ref
13831371
val generated: Tree = tpd.ref(ref).withSpan(span.startPos)
@@ -1431,6 +1419,30 @@ trait Implicits { self: Typer =>
14311419
}
14321420
}
14331421

1422+
/** An implicit search; parameters as in `inferImplicit` */
1423+
class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(implicit ctx: Context) {
1424+
assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType],
1425+
em"found: $argument: ${argument.tpe}, expected: $pt")
1426+
1427+
private def nestedContext() =
1428+
ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled)
1429+
1430+
private def implicitProto(resultType: Type, f: Type => Type) =
1431+
if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType))
1432+
// Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though.
1433+
1434+
private def isCoherent = pt.isRef(defn.EqlClass)
1435+
1436+
/** The expected type for the searched implicit */
1437+
@threadUnsafe lazy val fullProto: Type = implicitProto(pt, identity)
1438+
1439+
/** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */
1440+
val wildProto: Type = implicitProto(pt, wildApprox(_))
1441+
1442+
val isNot: Boolean = wildProto.classSymbol == defn.NotClass
1443+
1444+
//println(i"search implicits $pt / ${eligible.map(_.ref)}")
1445+
14341446
/** Try to type-check implicit reference, after checking that this is not
14351447
* a diverging search
14361448
*/
@@ -1440,7 +1452,7 @@ trait Implicits { self: Typer =>
14401452
else {
14411453
val history = ctx.searchHistory.nest(cand, pt)
14421454
val result =
1443-
typedImplicit(cand, contextual)(nestedContext().setNewTyperState().setFreshGADTBounds.setSearchHistory(history))
1455+
typedImplicit(cand, pt, argument, span)(nestedContext().setNewTyperState().setFreshGADTBounds.setSearchHistory(history))
14441456
result match {
14451457
case res: SearchSuccess =>
14461458
ctx.searchHistory.defineBynameImplicit(pt.widenExpr, res)

0 commit comments

Comments
 (0)