From 7cea87561f52d374e8b2d9135ec96ca889460a29 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 28 Dec 2019 23:12:34 +0100 Subject: [PATCH 01/27] Make "did you mean hints" less chatty Don't issue a "did you mean" hint if a short name gets as a hint a completely unrelated short name. --- .../dotty/tools/dotc/reporting/diagnostic/messages.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 9a64593c614f..bb56d91aa960 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -345,8 +345,10 @@ object messages { // Get closest match in `site` val closest = decls - .map { case (n, sym) => (n, distance(n, name.show), sym) } - .collect { case (n, dist, sym) if dist <= maxDist => (n, dist, sym) } + .map { (n, sym) => (n, distance(n, name.show), sym) } + .collect { + case (n, dist, sym) if dist <= maxDist && dist < name.toString.length => (n, dist, sym) + } .groupBy(_._2).toList .sortBy(_._1) .headOption.map(_._2).getOrElse(Nil) From 9a3c33a67f7762f2c905886505387b22a59d16fe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 28 Dec 2019 23:35:50 +0100 Subject: [PATCH 02/27] Make suggestions of missing implicits imports on type errors Add an addendum to an error message where the error might be fixed be some implicit argument or conversion or some extension method that is however not found. The addendum suggests suggests implicit imports that might fix the problem. --- .../tools/dotc/printing/PlainPrinter.scala | 2 +- .../dotty/tools/dotc/printing/Printer.scala | 5 +- .../dotty/tools/dotc/reporting/Reporter.scala | 1 + .../tools/dotc/typer/ErrorReporting.scala | 5 +- .../dotty/tools/dotc/typer/Implicits.scala | 143 +++++++++++++++--- .../dotty/tools/dotc/typer/TypeAssigner.scala | 10 +- tests/neg/missing-implicit.check | 55 +++++++ tests/neg/missing-implicit.scala | 12 ++ 8 files changed, 204 insertions(+), 29 deletions(-) create mode 100644 tests/neg/missing-implicit.check create mode 100644 tests/neg/missing-implicit.scala diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 8542caa8e637..e18964bb0e15 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -278,7 +278,7 @@ class PlainPrinter(_ctx: Context) extends Printer { } /** The string representation of this type used as a prefix */ - protected def toTextRef(tp: SingletonType): Text = controlled { + def toTextRef(tp: SingletonType): Text = controlled { tp match { case tp: TermRef => toTextPrefix(tp.prefix) ~ selectionString(tp) diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index e5a2a510436d..9d301a869f8d 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -4,7 +4,7 @@ package printing import core._ import Texts._, ast.Trees._ -import Types.Type, Symbols.Symbol, Scopes.Scope, Constants.Constant, +import Types.{Type, SingletonType}, Symbols.Symbol, Scopes.Scope, Constants.Constant, Names.Name, Denotations._, Annotations.Annotation import typer.Implicits.SearchResult import util.SourcePosition @@ -97,6 +97,9 @@ abstract class Printer { */ def toText(sym: Symbol): Text + /** Textual representation of singeton type reference */ + def toTextRef(tp: SingletonType): Text + /** Textual representation of symbol's declaration */ def dclText(sym: Symbol): Text diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 7db48670e03a..5afbb123143c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -15,6 +15,7 @@ import diagnostic.messages._ import diagnostic._ import ast.{tpd, Trees} import Message._ +import core.Decorators._ import java.lang.System.currentTimeMillis import java.io.{ BufferedReader, PrintWriter } diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index b74ad17a1311..3228803065c1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -150,7 +150,10 @@ object ErrorReporting { val expected1 = reported(expected) val (found2, expected2) = if (found1 frozen_<:< expected1) (found, expected) else (found1, expected1) - TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript) + val postScript1 = + if !postScript.isEmpty then postScript + else ctx.typer.implicitSuggestionsFor(ViewProto(found.widen, expected)) + TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript1) } /** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0d8111fbd642..67570cd2d89b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -37,6 +37,7 @@ import config.Printers.{implicits, implicitsDetailed} import collection.mutable import reporting.trace import annotation.tailrec +import scala.util.control.NonFatal import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -462,6 +463,66 @@ object Implicits { def explanation(implicit ctx: Context): String = em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" } + + /** A helper class to find imports of givens that might fix a type error. + * + * suggestions(p).search + * + * returns a list of TermRefs that refer to implicits or givens + * that satisfy predicate `p`. + * + * The search algorithm looks for givens in the smallest set of objects + * and packages that includes + * + * - any object that is a defined in an enclosing scope, + * - any object that is a member of an enclosing class, + * - any enclosing package (including the root package), + * - any object that is a member of a searched object or package, + * - any object or package from which something is imported in an enclosing scope, + * - any package that is nested in a searched package, provided + * the package was accessed in some way previously. + */ + class suggestions(qualifies: TermRef => Boolean) with + private val seen = mutable.Set[TermRef]() + + private def lookInside(root: Symbol)(given ctx: Context): Boolean = + !root.name.lastPart.contains('$') + && root.is(ModuleVal, butNot = JavaDefined) + && (root.isCompleted || !root.is(Package)) + + private def rootsIn(ref: TermRef)(given ctx: Context): List[TermRef] = + if seen.contains(ref) then Nil + else + implicitsDetailed.println(i"search in ${ref.symbol.fullName}") + seen += ref + ref :: ref.fields + .filter(fld => lookInside(fld.symbol)) + .map(fld => TermRef(ref, fld.symbol.asTerm)) + .flatMap(rootsIn) + .toList + + private def rootsOnPath(tp: Type)(given ctx: Context): List[TermRef] = tp match + case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) + case _ => Nil + + private def roots(given ctx: Context): List[TermRef] = + if ctx.owner.exists then + val defined = + if ctx.scope eq ctx.outer.scope then Nil + else ctx.scope + .filter(lookInside(_)) + .flatMap(sym => rootsIn(sym.termRef)) + val imported = + if ctx.importInfo eq ctx.outer.importInfo then Nil + else ctx.importInfo.sym.info match + case ImportType(expr) => rootsOnPath(expr.tpe) + case _ => Nil + defined ++ imported ++ roots(given ctx.outer) + else Nil + + def search(given ctx: Context): List[TermRef] = + roots.flatMap(_.implicitMembers.filter(qualifies)) + end suggestions } import Implicits._ @@ -683,6 +744,35 @@ trait Implicits { self: Typer => } } + /** An addendum to an error message where the error might be fixed + * be some implicit value of type `pt` that is however not found. + * The addendum suggests suggests implicit imports that might fix the problem. + */ + override def implicitSuggestionsFor(pt: Type)(given ctx: Context): String = + val suggestedRefs = + try Implicits.suggestions(_ <:< pt).search(given ctx.fresh.setExploreTyperState()) + catch case NonFatal(ex) => Nil + def refToRawString(ref: TermRef) = ctx.printer.toTextRef(ref).show + def refToString(ref: TermRef): String = + val raw = refToRawString(ref) + ref.prefix match + case prefix: TermRef if !raw.contains(".") => s"${refToRawString(prefix)}.$raw" + case _ => raw + def suggestStr(ref: TermRef) = i" import ${refToString(ref)}" + if suggestedRefs.isEmpty then "" + else + val suggestions = suggestedRefs.map(suggestStr).distinct + // TermRefs might be different but generate the same strings + val fix = + if suggestions.tail.isEmpty then "The following import" + else "One of the following imports" + i""" + | + |$fix might fix the problem: + | + |$suggestions%\n% + """ + /** Handlers to synthesize implicits for special types */ type SpecialHandler = (Type, Span) => Context => Tree type SpecialHandlers = List[(ClassSymbol, SpecialHandler)] @@ -1215,32 +1305,37 @@ trait Implicits { self: Typer => pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), pt.widenExpr.argInfos)) - def hiddenImplicitsAddendum: String = arg.tpe match { - case fail: SearchFailureType => - - def hiddenImplicitNote(s: SearchSuccess) = - em"\n\nNote: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`." + def hiddenImplicitsAddendum: String = + + def hiddenImplicitNote(s: SearchSuccess) = + em"\n\nNote: given instance ${s.ref.symbol.showLocated} was not considered because it was not imported with `import given`." + + def FindHiddenImplicitsCtx(ctx: Context): Context = + if (ctx == NoContext) ctx + else ctx.freshOver(FindHiddenImplicitsCtx(ctx.outer)).addMode(Mode.FindHiddenImplicits) + + val normalImports = arg.tpe match + case fail: SearchFailureType => + if (fail.expectedType eq pt) || isFullyDefined(fail.expectedType, ForceDegree.none) then + inferImplicit(fail.expectedType, fail.argument, arg.span)( + FindHiddenImplicitsCtx(ctx)) match { + case s: SearchSuccess => hiddenImplicitNote(s) + case f: SearchFailure => + f.reason match { + case ambi: AmbiguousImplicits => hiddenImplicitNote(ambi.alt1) + case r => "" + } + } + else + // It's unsafe to search for parts of the expected type if they are not fully defined, + // since these come with nested contexts that are lost at this point. See #7249 for an + // example where searching for a nested type causes an infinite loop. + "" - def FindHiddenImplicitsCtx(ctx: Context): Context = - if (ctx == NoContext) ctx - else ctx.freshOver(FindHiddenImplicitsCtx(ctx.outer)).addMode(Mode.FindHiddenImplicits) + def suggestedImports = implicitSuggestionsFor(pt) + if normalImports.isEmpty then suggestedImports else normalImports + end hiddenImplicitsAddendum - if (fail.expectedType eq pt) || isFullyDefined(fail.expectedType, ForceDegree.none) then - inferImplicit(fail.expectedType, fail.argument, arg.span)( - FindHiddenImplicitsCtx(ctx)) match { - case s: SearchSuccess => hiddenImplicitNote(s) - case f: SearchFailure => - f.reason match { - case ambi: AmbiguousImplicits => hiddenImplicitNote(ambi.alt1) - case r => "" - } - } - else - // It's unsafe to search for parts of the expected type if they are not fully defined, - // since these come with nested contexts that are lost at this point. See #7249 for an - // example where searching for a nested type causes an infinite loop. - "" - } msg(userDefined.getOrElse( em"no implicit argument of type $pt was found${location("for")}"))() ++ hiddenImplicitsAddendum diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index d31e2b6c1bfd..a4e9c22f78aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -11,6 +11,7 @@ import util.SourcePosition import config.Printers.typr import ast.Trees._ import NameOps._ +import ProtoTypes._ import collection.mutable import reporting.diagnostic.messages._ import Checking.{checkNoPrivateLeaks, checkNoWildcard} @@ -266,7 +267,7 @@ trait TypeAssigner { errorType(ex"$qualType does not have a constructor", tree.sourcePos) else { val kind = if (name.isTypeName) "type" else "value" - val addendum = + def addendum = if (qualType.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" else qual1.getAttachment(Typer.HiddenSearchFailure) match { @@ -281,12 +282,17 @@ trait TypeAssigner { |Note that `$name` is treated as an infix operator in Scala 3. |If you do not want that, insert a `;` or empty line in front |or drop any spaces behind the operator.""" - else "" + else + implicitSuggestionsFor( + ViewProto(qualType.widen, + SelectionProto(name, WildcardType, NoViewsAllowed, privateOK = false))) } errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos) } } + def implicitSuggestionsFor(pt: Type)(given Context): String = "" + /** The type of the selection in `tree`, where `qual1` is the typed qualifier part. * The selection type is additionally checked for accessibility. */ diff --git a/tests/neg/missing-implicit.check b/tests/neg/missing-implicit.check new file mode 100644 index 000000000000..24ae065ef4e0 --- /dev/null +++ b/tests/neg/missing-implicit.check @@ -0,0 +1,55 @@ +-- [E008] Member Not Found Error: tests/neg/missing-implicit.scala:5:25 ------------------------------------------------ +5 | case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error + | ^^^^^^^ + | value > is not a member of T + | + | One of the following imports might fix the problem: + | + | import math.Ordering.Implicits.infixOrderingOps + | import math.Ordered.orderingToOrdered + | +-- [E008] Member Not Found Error: tests/neg/missing-implicit.scala:5:51 ------------------------------------------------ +5 | case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error + | ^^^^^^^ + | value - is not a member of T + | + | One of the following imports might fix the problem: + | + | import math.Fractional.Implicits.infixFractionalOps + | import math.Numeric.Implicits.infixNumericOps + | import math.Integral.Implicits.infixIntegralOps + | +-- Error: tests/neg/missing-implicit.scala:10:24 ----------------------------------------------------------------------- +10 |val f = Future[Unit] { } // error + | ^ + | Cannot find an implicit ExecutionContext. You might pass + | an (implicit ec: ExecutionContext) parameter to your method. + | + | The ExecutionContext is used to configure how and on which + | thread pools Futures will run, so the specific ExecutionContext + | that is selected is important. + | + | If your application does not define an ExecutionContext elsewhere, + | consider using Scala's global ExecutionContext by defining + | the following: + | + | implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global + | + | The following import might fix the problem: + | + | import concurrent.ExecutionContext.Implicits.global + | +-- [E007] Type Mismatch Error: tests/neg/missing-implicit.scala:12:25 -------------------------------------------------- +12 |val b: java.lang.Byte = (1: Byte) // error + | ^^^^^^^ + | Found: Byte + | Required: Byte² + | + | where: Byte is a class in package scala + | Byte² is a class in package lang + | + | + | The following import might fix the problem: + | + | import Predef.byte2Byte + | diff --git a/tests/neg/missing-implicit.scala b/tests/neg/missing-implicit.scala new file mode 100644 index 000000000000..8de2394bd6c5 --- /dev/null +++ b/tests/neg/missing-implicit.scala @@ -0,0 +1,12 @@ +import Predef.{byte2Byte => _} +import math.Numeric + +def consume[T: Numeric](xs: List[T], limit: T): List[T] = xs match + case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error + case _ => xs + +import scala.concurrent.Future + +val f = Future[Unit] { } // error + +val b: java.lang.Byte = (1: Byte) // error From 53a6098fd8e51d2d42726400bb47b4d00fa2dbde Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Dec 2019 10:29:01 +0100 Subject: [PATCH 03/27] Sort suggestions alphabetically This is needed to get stability of test outputs. But we should try to find more useful sorting criteria. --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 5 +++-- compiler/test-resources/repl/importFromObj | 4 ++-- tests/neg/missing-implicit.check | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 67570cd2d89b..8d0e1fa55eac 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -761,8 +761,9 @@ trait Implicits { self: Typer => def suggestStr(ref: TermRef) = i" import ${refToString(ref)}" if suggestedRefs.isEmpty then "" else - val suggestions = suggestedRefs.map(suggestStr).distinct - // TermRefs might be different but generate the same strings + val suggestions = suggestedRefs.map(suggestStr) + .distinct // TermRefs might be different but generate the same strings + .sorted // To get test stability. TODO: Find more useful sorting criteria val fix = if suggestions.tail.isEmpty then "The following import" else "One of the following imports" diff --git a/compiler/test-resources/repl/importFromObj b/compiler/test-resources/repl/importFromObj index 31753dfbd821..4192e6d8d4c5 100644 --- a/compiler/test-resources/repl/importFromObj +++ b/compiler/test-resources/repl/importFromObj @@ -14,8 +14,8 @@ val res0: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) scala> import util.foo 1 | import util.foo | ^^^ - | value foo is not a member of util - did you mean util.Try? + | value foo is not a member of util scala> import util.foo.bar 1 | import util.foo.bar | ^^^^^^^^ - | value foo is not a member of util - did you mean util.Try? + | value foo is not a member of util diff --git a/tests/neg/missing-implicit.check b/tests/neg/missing-implicit.check index 24ae065ef4e0..3c8230f8c446 100644 --- a/tests/neg/missing-implicit.check +++ b/tests/neg/missing-implicit.check @@ -5,8 +5,8 @@ | | One of the following imports might fix the problem: | - | import math.Ordering.Implicits.infixOrderingOps | import math.Ordered.orderingToOrdered + | import math.Ordering.Implicits.infixOrderingOps | -- [E008] Member Not Found Error: tests/neg/missing-implicit.scala:5:51 ------------------------------------------------ 5 | case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error @@ -16,8 +16,8 @@ | One of the following imports might fix the problem: | | import math.Fractional.Implicits.infixFractionalOps - | import math.Numeric.Implicits.infixNumericOps | import math.Integral.Implicits.infixIntegralOps + | import math.Numeric.Implicits.infixNumericOps | -- Error: tests/neg/missing-implicit.scala:10:24 ----------------------------------------------------------------------- 10 |val f = Future[Unit] { } // error From b848c57dbde3d754f04261276993a432b3909476 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Dec 2019 11:40:34 +0100 Subject: [PATCH 04/27] Don't treat package object's methods as package members --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 62a408209c20..8729abe877a8 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2205,7 +2205,7 @@ object SymDenotations { if (symbol `eq` defn.ScalaPackageClass) { val denots = super.computeNPMembersNamed(name) - if (denots.exists) denots + if (denots.exists || name == nme.CONSTRUCTOR) denots else recur(packageObjs, NoDenotation) } else recur(packageObjs, NoDenotation) From 13c18394fa9a66ebb110722caba911b452036754 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Dec 2019 11:50:07 +0100 Subject: [PATCH 05/27] Refine search and reporting criteria - Do search in completed packages (this did not work before for nested packages) - Don't search in Java-defined objects - Don't suggest any of the root imports that are imported by default: There might be a deeper problem, or the import was disabled intentionally. In either case it makes no sense to suggest the import again. --- .../dotty/tools/dotc/typer/Implicits.scala | 26 +++++++++++++------ tests/neg/missing-implicit.check | 16 +++++++++--- tests/neg/missing-implicit.scala | 6 ++++- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 8d0e1fa55eac..ee7ec42be61b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -14,7 +14,7 @@ import Flags._ import TypeErasure.{erasure, hasStableErasure} import Mode.ImplicitsEnabled import NameOps._ -import NameKinds.LazyImplicitName +import NameKinds.{LazyImplicitName, FlatName} import Symbols._ import Denotations._ import Types._ @@ -486,18 +486,25 @@ object Implicits { private val seen = mutable.Set[TermRef]() private def lookInside(root: Symbol)(given ctx: Context): Boolean = - !root.name.lastPart.contains('$') - && root.is(ModuleVal, butNot = JavaDefined) - && (root.isCompleted || !root.is(Package)) + if root.is(PackageVal) then root.isCompleted + else !root.name.is(FlatName) + && !root.name.lastPart.contains('$') + && root.is(ModuleVal, butNot = JavaDefined) private def rootsIn(ref: TermRef)(given ctx: Context): List[TermRef] = if seen.contains(ref) then Nil else implicitsDetailed.println(i"search in ${ref.symbol.fullName}") seen += ref - ref :: ref.fields - .filter(fld => lookInside(fld.symbol)) - .map(fld => TermRef(ref, fld.symbol.asTerm)) + val nested = + if ref.symbol.is(Package) then + ref.info.decls.filter(lookInside) + else + ref.symbol.ensureCompleted() // JavaDefined in reliably known only after completion + if ref.symbol.is(JavaDefined) then Nil + else ref.fields.map(_.symbol).filter(lookInside) + ref :: nested + .map(mbr => TermRef(ref, mbr.asTerm)) .flatMap(rootsIn) .toList @@ -521,7 +528,10 @@ object Implicits { else Nil def search(given ctx: Context): List[TermRef] = - roots.flatMap(_.implicitMembers.filter(qualifies)) + roots + .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) + // don't suggest things that are imported by default + .flatMap(_.implicitMembers.filter(qualifies)) end suggestions } diff --git a/tests/neg/missing-implicit.check b/tests/neg/missing-implicit.check index 3c8230f8c446..3159c16597d6 100644 --- a/tests/neg/missing-implicit.check +++ b/tests/neg/missing-implicit.check @@ -40,16 +40,24 @@ | import concurrent.ExecutionContext.Implicits.global | -- [E007] Type Mismatch Error: tests/neg/missing-implicit.scala:12:25 -------------------------------------------------- -12 |val b: java.lang.Byte = (1: Byte) // error +12 |val b: java.lang.Byte = (1: Byte) // error, but no hint | ^^^^^^^ | Found: Byte | Required: Byte² | | where: Byte is a class in package scala | Byte² is a class in package lang +-- [E007] Type Mismatch Error: tests/neg/missing-implicit.scala:16:44 -------------------------------------------------- +16 |val d: scala.concurrent.duration.Duration = (10, DAYS) // error + | ^^^^^^^^^^ + | Found: (Int, java.util.concurrent.TimeUnit) + | Required: concurrent².duration.Duration | + | where: concurrent is a package in package util + | concurrent² is a package in package scala | - | The following import might fix the problem: | - | import Predef.byte2Byte - | + | The following import might fix the problem: + | + | import concurrent.duration.pairIntToDuration + | diff --git a/tests/neg/missing-implicit.scala b/tests/neg/missing-implicit.scala index 8de2394bd6c5..c630e77ce30b 100644 --- a/tests/neg/missing-implicit.scala +++ b/tests/neg/missing-implicit.scala @@ -9,4 +9,8 @@ import scala.concurrent.Future val f = Future[Unit] { } // error -val b: java.lang.Byte = (1: Byte) // error +val b: java.lang.Byte = (1: Byte) // error, but no hint + +val DAYS = scala.concurrent.duration.DAYS + +val d: scala.concurrent.duration.Duration = (10, DAYS) // error From e1360eb2525fc89c0b3416b143d604fdf5c250f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Dec 2019 12:35:20 +0100 Subject: [PATCH 06/27] Don't suggest imports for conversions from Null or Nothing nor for conversions to Any, AnyRef, or AnyVal. --- compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 3228803065c1..9a1941bcc982 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -151,7 +151,12 @@ object ErrorReporting { val (found2, expected2) = if (found1 frozen_<:< expected1) (found, expected) else (found1, expected1) val postScript1 = - if !postScript.isEmpty then postScript + if !postScript.isEmpty + || expected.isRef(defn.AnyClass) + || expected.isRef(defn.AnyValClass) + || expected.isRef(defn.ObjectClass) + || defn.isBottomType(found) + then postScript else ctx.typer.implicitSuggestionsFor(ViewProto(found.widen, expected)) TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript1) } From 5e3f11cd4c4d654508745f35fc0414e97a45978e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Dec 2019 18:37:41 +0100 Subject: [PATCH 07/27] Implement cancellation Add an `isCancelled` variable to `Run`. If this variable is true, `typed` and `typedImplicit` calls will return immediately. --- compiler/src/dotty/tools/dotc/Run.scala | 6 ++++++ compiler/src/dotty/tools/dotc/typer/Implicits.scala | 1 + compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 +++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 91dd4896fdfb..07ea4182f79f 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -31,6 +31,12 @@ import scala.util.control.NonFatal /** A compiler run. Exports various methods to compile source files */ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo { + /** If this variable is set to `true`, some core typer operations will + * return immediately. Currently these early abort operatoons are + * `Typer.typed` and `Implicits.typedImplicit`. + */ + var isCancelled = false + /** Produces the following contexts, from outermost to innermost * * bootStrap: A context with next available runId and a scope consisting of diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ee7ec42be61b..22cf3c4271fb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1484,6 +1484,7 @@ trait Implicits { self: Typer => /** Try to typecheck an implicit reference */ 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) { + if ctx.run.isCancelled then return NoMatchingImplicitsFailure record("typedImplicit") val ref = cand.ref val generated: Tree = tpd.ref(ref).withSpan(span.startPos) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 87c5c93db675..4179aeb92c6f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2210,7 +2210,9 @@ class Typer extends Namer if (tree.source != ctx.source && tree.source.exists) typed(tree, pt, locked)(ctx.withSource(tree.source)) else - try adapt(typedUnadapted(tree, pt, locked), pt, locked) + try + if ctx.run.isCancelled then tree.withType(WildcardType) + else adapt(typedUnadapted(tree, pt, locked), pt, locked) catch { case ex: TypeError => errorTree(tree, ex, tree.sourcePos.focus) From 540f69ad31c877e88aac9a26ba881a50fe84375b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Dec 2019 19:17:54 +0100 Subject: [PATCH 08/27] Subject implicit suggestion searches to timeouts Currently: Max 1 sec to test a single implicit Max 10 secs to make suggestions for a missing implicit import --- compiler/src/dotty/tools/dotc/Run.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 30 ++++++++++++++--- .../src/dotty/tools/dotc/util/Scheduler.scala | 33 +++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/util/Scheduler.scala diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 07ea4182f79f..be8e7a59c183 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -35,7 +35,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint * return immediately. Currently these early abort operatoons are * `Typer.typed` and `Implicits.typedImplicit`. */ - var isCancelled = false + @volatile var isCancelled = false /** Produces the following contexts, from outermost to innermost * diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 22cf3c4271fb..58edd795cadf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -31,7 +31,7 @@ import transform.SymUtils._ import transform.TypeUtils._ import transform.SyntheticMembers._ import Hashable._ -import util.{Property, SourceFile, NoSource} +import util.{Property, SourceFile, NoSource, Scheduler} import config.Config import config.Printers.{implicits, implicitsDetailed} import collection.mutable @@ -68,6 +68,9 @@ object Implicits { final val Extension = 4 } + val testOneImplicitTimeOut = 1000 + val suggestImplicitTimeOut = 10000 + /** A common base class of contextual implicits and of-type implicits which * represents a set of references to implicit definitions. */ @@ -528,10 +531,29 @@ object Implicits { else Nil def search(given ctx: Context): List[TermRef] = - roots - .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) + val scheduler = new Scheduler() + val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut + + def test(ref: TermRef)(given Context): Boolean = + System.currentTimeMillis < deadLine + && { + scheduler.scheduleAfter(testOneImplicitTimeOut) { + println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + ctx.run.isCancelled = true + } + try qualifies(ref) + finally + scheduler.cancel() + ctx.run.isCancelled = false + } + + try + roots + .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) // don't suggest things that are imported by default - .flatMap(_.implicitMembers.filter(qualifies)) + .flatMap(_.implicitMembers.filter(test)) + finally scheduler.shutdown() + end search end suggestions } diff --git a/compiler/src/dotty/tools/dotc/util/Scheduler.scala b/compiler/src/dotty/tools/dotc/util/Scheduler.scala new file mode 100644 index 000000000000..ee3cb4ef72dd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/Scheduler.scala @@ -0,0 +1,33 @@ +package dotty.tools.dotc.util +class Scheduler extends Thread with + + private var execTime: Long = Long.MaxValue + private var task: () => Unit = _ + private var stopped: Boolean = false + + def scheduleAfter(after: Long)(task: => Unit) = synchronized { + this.task = () => task + this.execTime = System.currentTimeMillis + after + } + + def cancel(): Unit = synchronized { + execTime = Long.MaxValue + } + + def shutdown(): Unit = synchronized { + stopped = true + } + + def poll(): Unit = synchronized { + if System.currentTimeMillis() > execTime then + task() + execTime = Long.MaxValue + } + + override def run(): Unit = + while !stopped do + poll() + Thread.sleep(1) + + start() +end Scheduler \ No newline at end of file From 2ccec1f62699e0e9cd4916053795d79bcd1562e8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Jan 2020 09:37:29 +0100 Subject: [PATCH 09/27] Streamline suggestion printing --- .../dotty/tools/dotc/typer/Implicits.scala | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 58edd795cadf..b5da5ee06429 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -497,7 +497,7 @@ object Implicits { private def rootsIn(ref: TermRef)(given ctx: Context): List[TermRef] = if seen.contains(ref) then Nil else - implicitsDetailed.println(i"search in ${ref.symbol.fullName}") + implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref val nested = if ref.symbol.is(Package) then @@ -538,7 +538,7 @@ object Implicits { System.currentTimeMillis < deadLine && { scheduler.scheduleAfter(testOneImplicitTimeOut) { - println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + implicitsDetailed.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") ctx.run.isCancelled = true } try qualifies(ref) @@ -550,7 +550,7 @@ object Implicits { try roots .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) - // don't suggest things that are imported by default + // don't suggest things that are imported by default .flatMap(_.implicitMembers.filter(test)) finally scheduler.shutdown() end search @@ -784,18 +784,14 @@ trait Implicits { self: Typer => val suggestedRefs = try Implicits.suggestions(_ <:< pt).search(given ctx.fresh.setExploreTyperState()) catch case NonFatal(ex) => Nil - def refToRawString(ref: TermRef) = ctx.printer.toTextRef(ref).show - def refToString(ref: TermRef): String = - val raw = refToRawString(ref) - ref.prefix match - case prefix: TermRef if !raw.contains(".") => s"${refToRawString(prefix)}.$raw" - case _ => raw - def suggestStr(ref: TermRef) = i" import ${refToString(ref)}" - if suggestedRefs.isEmpty then "" + def importString(ref: TermRef): String = + s" import ${ctx.printer.toTextRef(ref).show}" + val suggestions = suggestedRefs.map(importString) + .filter(_.contains('.')) + .distinct // TermRefs might be different but generate the same strings + .sorted // To get test stability. TODO: Find more useful sorting criteria + if suggestions.isEmpty then "" else - val suggestions = suggestedRefs.map(suggestStr) - .distinct // TermRefs might be different but generate the same strings - .sorted // To get test stability. TODO: Find more useful sorting criteria val fix = if suggestions.tail.isEmpty then "The following import" else "One of the following imports" @@ -805,6 +801,7 @@ trait Implicits { self: Typer => | |$suggestions%\n% """ + end implicitSuggestionsFor /** Handlers to synthesize implicits for special types */ type SpecialHandler = (Type, Span) => Context => Tree From 51f19ec3b52717b86c2cc77af847504c0e8abda5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Jan 2020 22:09:42 +0100 Subject: [PATCH 10/27] Use java.util.Timer Also, implement other review suggestions --- compiler/src/dotty/tools/dotc/Run.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 27 ++++++++++++------- tests/neg/missing-implicit.scala | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index be8e7a59c183..a6f99c21f635 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -32,7 +32,7 @@ import scala.util.control.NonFatal class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo { /** If this variable is set to `true`, some core typer operations will - * return immediately. Currently these early abort operatoons are + * return immediately. Currently these early abort operations are * `Typer.typed` and `Implicits.typedImplicit`. */ @volatile var isCancelled = false diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index b5da5ee06429..dda9e73d67a1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -31,13 +31,14 @@ import transform.SymUtils._ import transform.TypeUtils._ import transform.SyntheticMembers._ import Hashable._ -import util.{Property, SourceFile, NoSource, Scheduler} +import util.{Property, SourceFile, NoSource} import config.Config import config.Printers.{implicits, implicitsDetailed} import collection.mutable import reporting.trace import annotation.tailrec import scala.util.control.NonFatal +import java.util.{Timer, TimerTask} import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -68,7 +69,10 @@ object Implicits { final val Extension = 4 } - val testOneImplicitTimeOut = 1000 + /** Timeout to test a single implicit value as a suggestion, in ms */ + val testOneImplicitTimeOut = 500 + + /** Global timeout to stop looking for further implicit suggestions, in ms */ val suggestImplicitTimeOut = 10000 /** A common base class of contextual implicits and of-type implicits which @@ -531,19 +535,21 @@ object Implicits { else Nil def search(given ctx: Context): List[TermRef] = - val scheduler = new Scheduler() + val timer = new Timer() val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut def test(ref: TermRef)(given Context): Boolean = System.currentTimeMillis < deadLine && { - scheduler.scheduleAfter(testOneImplicitTimeOut) { - implicitsDetailed.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") - ctx.run.isCancelled = true + val task = new TimerTask { + def run() = + implicitsDetailed.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + ctx.run.isCancelled = true } + timer.schedule(task, testOneImplicitTimeOut) try qualifies(ref) finally - scheduler.cancel() + task.cancel() ctx.run.isCancelled = false } @@ -552,7 +558,7 @@ object Implicits { .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) // don't suggest things that are imported by default .flatMap(_.implicitMembers.filter(test)) - finally scheduler.shutdown() + finally timer.cancel() end search end suggestions } @@ -777,8 +783,9 @@ trait Implicits { self: Typer => } /** An addendum to an error message where the error might be fixed - * be some implicit value of type `pt` that is however not found. - * The addendum suggests suggests implicit imports that might fix the problem. + * by some implicit value of type `pt` that is however not found. + * The addendum suggests given imports that might fix the problem. + * If there's nothing to suggest, an empty string is returned. */ override def implicitSuggestionsFor(pt: Type)(given ctx: Context): String = val suggestedRefs = diff --git a/tests/neg/missing-implicit.scala b/tests/neg/missing-implicit.scala index c630e77ce30b..3e786f28aa27 100644 --- a/tests/neg/missing-implicit.scala +++ b/tests/neg/missing-implicit.scala @@ -1,4 +1,4 @@ -import Predef.{byte2Byte => _} +import Predef.{byte2Byte => _, _} import math.Numeric def consume[T: Numeric](xs: List[T], limit: T): List[T] = xs match From 55b1a31d6d9b4e5a5d57f46a19ff0292cd64767d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 2 Jan 2020 09:49:18 +0100 Subject: [PATCH 11/27] Drop Scheduler --- .../src/dotty/tools/dotc/util/Scheduler.scala | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/util/Scheduler.scala diff --git a/compiler/src/dotty/tools/dotc/util/Scheduler.scala b/compiler/src/dotty/tools/dotc/util/Scheduler.scala deleted file mode 100644 index ee3cb4ef72dd..000000000000 --- a/compiler/src/dotty/tools/dotc/util/Scheduler.scala +++ /dev/null @@ -1,33 +0,0 @@ -package dotty.tools.dotc.util -class Scheduler extends Thread with - - private var execTime: Long = Long.MaxValue - private var task: () => Unit = _ - private var stopped: Boolean = false - - def scheduleAfter(after: Long)(task: => Unit) = synchronized { - this.task = () => task - this.execTime = System.currentTimeMillis + after - } - - def cancel(): Unit = synchronized { - execTime = Long.MaxValue - } - - def shutdown(): Unit = synchronized { - stopped = true - } - - def poll(): Unit = synchronized { - if System.currentTimeMillis() > execTime then - task() - execTime = Long.MaxValue - } - - override def run(): Unit = - while !stopped do - poll() - Thread.sleep(1) - - start() -end Scheduler \ No newline at end of file From 9c67ee485d8777bda1436929959591b3e8150932 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 2 Jan 2020 13:33:22 +0100 Subject: [PATCH 12/27] Revise search logic - Also search in nested objects - Avoid calling `fields` since this can cause cyclic reference exceptions (e.g. in neg/i2006.scala). --- .../dotty/tools/dotc/core/TypeErrors.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 73 ++++++++++++++----- tests/neg/missing-implicit1.check | 36 +++++++++ tests/neg/missing-implicit1.scala | 37 ++++++++++ 4 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 tests/neg/missing-implicit1.check create mode 100644 tests/neg/missing-implicit1.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index e4ae285ce3c1..adf0f1a75583 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -153,7 +153,7 @@ object CyclicReference { def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = { val ex = new CyclicReference(denot) if (!(ctx.mode is Mode.CheckCyclic)) { - cyclicErrors.println(ex.getMessage) + cyclicErrors.println(s"Cyclic reference involving $denot") for (elem <- ex.getStackTrace take 200) cyclicErrors.println(elem.toString) } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index dda9e73d67a1..70f329e8b7dc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -490,30 +490,55 @@ object Implicits { * the package was accessed in some way previously. */ class suggestions(qualifies: TermRef => Boolean) with + private type RootRef = TermRef | ThisType + + private def symbolOf(ref: RootRef)(given Context) = ref match + case ref: TermRef => ref.symbol + case ref: ThisType => ref.cls + private val seen = mutable.Set[TermRef]() private def lookInside(root: Symbol)(given ctx: Context): Boolean = - if root.is(PackageVal) then root.isCompleted + if root.is(Package) then root.isTerm && root.isCompleted else !root.name.is(FlatName) && !root.name.lastPart.contains('$') && root.is(ModuleVal, butNot = JavaDefined) + def nestedRoots(ref: RootRef)(given Context): List[Symbol] = + val seenNames = mutable.Set[Name]() + ref.widen.baseClasses.flatMap { bc => + bc.info.decls.filter { dcl => + lookInside(dcl) + && !seenNames.contains(dcl.name) + && { seenNames += dcl.name; true } + } + } + + private def rootsStrictlyIn(ref: RootRef)(given ctx: Context): List[TermRef] = + val refSym = symbolOf(ref) + val nested = + if refSym == defn.EmptyPackageClass // Don't search the empty package, either as enclosing package ... + || refSym == defn.EmptyPackageVal // ... or as a member of _root_. + || refSym == defn.JavaPackageVal // As an optimization, don't search java... + || refSym == defn.JavaLangPackageVal // ... or java.lang. + then Nil + else if refSym.is(Package) || refSym.isPackageObject then + refSym.info.decls.filter(lookInside) + else + if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion + if refSym.is(JavaDefined) then Nil + else nestedRoots(ref) + nested + .map(mbr => TermRef(ref, mbr.asTerm)) + .flatMap(rootsIn) + .toList + private def rootsIn(ref: TermRef)(given ctx: Context): List[TermRef] = if seen.contains(ref) then Nil else - implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}") + implicits.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref - val nested = - if ref.symbol.is(Package) then - ref.info.decls.filter(lookInside) - else - ref.symbol.ensureCompleted() // JavaDefined in reliably known only after completion - if ref.symbol.is(JavaDefined) then Nil - else ref.fields.map(_.symbol).filter(lookInside) - ref :: nested - .map(mbr => TermRef(ref, mbr.asTerm)) - .flatMap(rootsIn) - .toList + ref :: rootsStrictlyIn(ref) private def rootsOnPath(tp: Type)(given ctx: Context): List[TermRef] = tp match case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) @@ -522,8 +547,15 @@ object Implicits { private def roots(given ctx: Context): List[TermRef] = if ctx.owner.exists then val defined = - if ctx.scope eq ctx.outer.scope then Nil - else ctx.scope + if ctx.owner.isClass then + if ctx.owner eq ctx.outer.owner then Nil + else ctx.owner.thisType match + case ref: TermRef => rootsStrictlyIn(ref) + case ref: ThisType => rootsStrictlyIn(ref) + case _ => Nil + else if ctx.scope eq ctx.outer.scope then Nil + else + ctx.scope .filter(lookInside(_)) .flatMap(sym => rootsIn(sym.termRef)) val imported = @@ -543,7 +575,7 @@ object Implicits { && { val task = new TimerTask { def run() = - implicitsDetailed.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + implicits.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") ctx.run.isCancelled = true } timer.schedule(task, testOneImplicitTimeOut) @@ -558,6 +590,12 @@ object Implicits { .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) // don't suggest things that are imported by default .flatMap(_.implicitMembers.filter(test)) + catch + case ex: Throwable => + if ctx.settings.Ydebug.value then + println("caught exceptioon when searching for suggestions") + ex.printStackTrace() + Nil finally timer.cancel() end search end suggestions @@ -789,8 +827,7 @@ trait Implicits { self: Typer => */ override def implicitSuggestionsFor(pt: Type)(given ctx: Context): String = val suggestedRefs = - try Implicits.suggestions(_ <:< pt).search(given ctx.fresh.setExploreTyperState()) - catch case NonFatal(ex) => Nil + Implicits.suggestions(_ <:< pt).search(given ctx.fresh.setExploreTyperState()) def importString(ref: TermRef): String = s" import ${ctx.printer.toTextRef(ref).show}" val suggestions = suggestedRefs.map(importString) diff --git a/tests/neg/missing-implicit1.check b/tests/neg/missing-implicit1.check new file mode 100644 index 000000000000..b267d5d4d7a3 --- /dev/null +++ b/tests/neg/missing-implicit1.check @@ -0,0 +1,36 @@ +-- Error: tests/neg/missing-implicit1.scala:16:4 ----------------------------------------------------------------------- +16 | ff // error + | ^ + |no implicit argument of type testObjectInstance.Zip[Option] was found for parameter xs of method ff in object testObjectInstance + | + |The following import might fix the problem: + | + | import testObjectInstance.instances.zipOption + | +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:18:16 ---------------------------------------------- +18 | List(1, 2, 3).traverse(x => Option(x)) // error + | ^^^^^^^^^^^^^^^^^^^^^^ + | value traverse is not a member of List[Int] - did you mean List[Int].reverse? + | + | The following import might fix the problem: + | + | import testObjectInstance.instances.traverseList + | +-- Error: tests/neg/missing-implicit1.scala:35:4 ----------------------------------------------------------------------- +35 | ff // error + | ^ + | no implicit argument of type Zip[Option] was found for parameter xs of method ff + | + | The following import might fix the problem: + | + | import instances.zipOption + | +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:37:16 ---------------------------------------------- +37 | List(1, 2, 3).traverse(x => Option(x)) // error + | ^^^^^^^^^^^^^^^^^^^^^^ + | value traverse is not a member of List[Int] - did you mean List[Int].reverse? + | + | The following import might fix the problem: + | + | import instances.traverseList + | diff --git a/tests/neg/missing-implicit1.scala b/tests/neg/missing-implicit1.scala new file mode 100644 index 000000000000..bb74741bb2e4 --- /dev/null +++ b/tests/neg/missing-implicit1.scala @@ -0,0 +1,37 @@ +object testObjectInstance with + trait Zip[F[_]] + trait Traverse[F[_]] { + def [A, B, G[_] : Zip](fa: F[A]) traverse(f: A => G[B]): G[F[B]] + } + + object instances { + given zipOption: Zip[Option] = ??? + given traverseList: Traverse[List] = ??? + } + + def ff(given xs: Zip[Option]) = ??? + + //import instances.zipOption + + ff // error + + List(1, 2, 3).traverse(x => Option(x)) // error + +def testLocalInstance = + trait Zip[F[_]] + trait Traverse[F[_]] { + def [A, B, G[_] : Zip](fa: F[A]) traverse(f: A => G[B]): G[F[B]] + } + + object instances { + given zipOption: Zip[Option] = ??? + given traverseList: Traverse[List] = ??? + } + + def ff(given xs: Zip[Option]) = ??? + + //import instances.zipOption + + ff // error + + List(1, 2, 3).traverse(x => Option(x)) // error From b3a49667361827a4891e2b251bae0daf765767e8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 2 Jan 2020 14:26:29 +0100 Subject: [PATCH 13/27] Some refinements --- .../dotty/tools/dotc/core/Definitions.scala | 2 + .../dotty/tools/dotc/typer/Implicits.scala | 54 ++++++++----------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9ae1b38c8a41..127e6647f204 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -210,7 +210,9 @@ class Definitions { } @tu lazy val ScalaPackageObject: Symbol = ctx.requiredModule("scala.package") @tu lazy val JavaPackageVal: TermSymbol = ctx.requiredPackage(nme.java) + @tu lazy val JavaPackageClass: ClassSymbol = JavaPackageVal.moduleClass.asClass @tu lazy val JavaLangPackageVal: TermSymbol = ctx.requiredPackage(jnme.JavaLang) + @tu lazy val JavaLangPackageClass: ClassSymbol = JavaLangPackageVal.moduleClass.asClass // fundamental modules @tu lazy val SysPackage : Symbol = ctx.requiredModule("scala.sys.package") diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 70f329e8b7dc..2ba8874bc895 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -490,23 +490,17 @@ object Implicits { * the package was accessed in some way previously. */ class suggestions(qualifies: TermRef => Boolean) with - private type RootRef = TermRef | ThisType - - private def symbolOf(ref: RootRef)(given Context) = ref match - case ref: TermRef => ref.symbol - case ref: ThisType => ref.cls - private val seen = mutable.Set[TermRef]() - private def lookInside(root: Symbol)(given ctx: Context): Boolean = + private def lookInside(root: Symbol)(given Context): Boolean = if root.is(Package) then root.isTerm && root.isCompleted else !root.name.is(FlatName) && !root.name.lastPart.contains('$') && root.is(ModuleVal, butNot = JavaDefined) - def nestedRoots(ref: RootRef)(given Context): List[Symbol] = + def nestedRoots(site: Type)(given Context): List[Symbol] = val seenNames = mutable.Set[Name]() - ref.widen.baseClasses.flatMap { bc => + site.baseClasses.flatMap { bc => bc.info.decls.filter { dcl => lookInside(dcl) && !seenNames.contains(dcl.name) @@ -514,33 +508,33 @@ object Implicits { } } - private def rootsStrictlyIn(ref: RootRef)(given ctx: Context): List[TermRef] = - val refSym = symbolOf(ref) + private def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] = + val site = ref.widen + val refSym = site.typeSymbol val nested = - if refSym == defn.EmptyPackageClass // Don't search the empty package, either as enclosing package ... - || refSym == defn.EmptyPackageVal // ... or as a member of _root_. - || refSym == defn.JavaPackageVal // As an optimization, don't search java... - || refSym == defn.JavaLangPackageVal // ... or java.lang. - then Nil - else if refSym.is(Package) || refSym.isPackageObject then - refSym.info.decls.filter(lookInside) + if refSym.is(Package) then + if refSym == defn.EmptyPackageClass // Don't search the empty package + || refSym == defn.JavaPackageClass // As an optimization, don't search java... + || refSym == defn.JavaLangPackageClass // ... or java.lang. + then Nil + else refSym.info.decls.filter(lookInside) else if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion if refSym.is(JavaDefined) then Nil - else nestedRoots(ref) + else nestedRoots(site) nested .map(mbr => TermRef(ref, mbr.asTerm)) .flatMap(rootsIn) .toList - private def rootsIn(ref: TermRef)(given ctx: Context): List[TermRef] = + private def rootsIn(ref: TermRef)(given Context): List[TermRef] = if seen.contains(ref) then Nil else implicits.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref ref :: rootsStrictlyIn(ref) - private def rootsOnPath(tp: Type)(given ctx: Context): List[TermRef] = tp match + private def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) case _ => Nil @@ -549,15 +543,12 @@ object Implicits { val defined = if ctx.owner.isClass then if ctx.owner eq ctx.outer.owner then Nil - else ctx.owner.thisType match - case ref: TermRef => rootsStrictlyIn(ref) - case ref: ThisType => rootsStrictlyIn(ref) - case _ => Nil - else if ctx.scope eq ctx.outer.scope then Nil + else rootsStrictlyIn(ctx.owner.thisType) else - ctx.scope - .filter(lookInside(_)) - .flatMap(sym => rootsIn(sym.termRef)) + if ctx.scope eq ctx.outer.scope then Nil + else ctx.scope + .filter(lookInside(_)) + .flatMap(sym => rootsIn(sym.termRef)) val imported = if ctx.importInfo eq ctx.outer.importInfo then Nil else ctx.importInfo.sym.info match @@ -573,11 +564,10 @@ object Implicits { def test(ref: TermRef)(given Context): Boolean = System.currentTimeMillis < deadLine && { - val task = new TimerTask { + val task = new TimerTask with def run() = implicits.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") ctx.run.isCancelled = true - } timer.schedule(task, testOneImplicitTimeOut) try qualifies(ref) finally @@ -593,7 +583,7 @@ object Implicits { catch case ex: Throwable => if ctx.settings.Ydebug.value then - println("caught exceptioon when searching for suggestions") + println("caught exception when searching for suggestions") ex.printStackTrace() Nil finally timer.cancel() From 717cb7d9eb83d4bee7f030c3a089a5916f70fda8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 4 Jan 2020 12:40:08 +0100 Subject: [PATCH 14/27] Refine implicit search criterion and prioritization Distinguish between confirmed matches for which dependent implicits can be found, and partial matches, for which these dependent implicits might require additional imports. Show partial matches only if there are no confirmed matches. Also, if there are multiple suggestions prioritize according to specificity. --- .../dotty/tools/dotc/typer/Implicits.scala | 356 ++++++++++-------- tests/neg/missing-implicit.check | 19 +- tests/neg/missing-implicit.scala | 2 + tests/neg/missing-implicit1.check | 38 +- tests/neg/missing-implicit1.scala | 14 +- 5 files changed, 252 insertions(+), 177 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 2ba8874bc895..e0006bd1e62a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -75,6 +75,14 @@ object Implicits { /** Global timeout to stop looking for further implicit suggestions, in ms */ val suggestImplicitTimeOut = 10000 + /** If `expected` is a selection prototype, does `tp` have an extension + * method with the selecting name? False otherwise. + */ + def hasExtMethod(tp: Type, expected: Type)(given Context) = expected match + case SelectionProto(name, _, _, _) => + tp.memberBasedOnFlags(name, required = ExtensionMethod).exists + case _ => false + /** A common base class of contextual implicits and of-type implicits which * represents a set of references to implicit definitions. */ @@ -155,11 +163,7 @@ object Implicits { val isImplicitConversion = tpw.derivesFrom(defn.ConversionClass) // An implementation of <:< counts as a view val isConforms = tpw.derivesFrom(defn.SubTypeClass) - val hasExtensions = resType match { - case SelectionProto(name, _, _, _) => - tpw.memberBasedOnFlags(name, required = ExtensionMethod).exists - case _ => false - } + val hasExtensions = hasExtMethod(tpw, resType) val conversionKind = if (isFunctionInS2 || isImplicitConversion || isConforms) Candidate.Conversion else Candidate.None @@ -470,125 +474,6 @@ object Implicits { def explanation(implicit ctx: Context): String = em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" } - - /** A helper class to find imports of givens that might fix a type error. - * - * suggestions(p).search - * - * returns a list of TermRefs that refer to implicits or givens - * that satisfy predicate `p`. - * - * The search algorithm looks for givens in the smallest set of objects - * and packages that includes - * - * - any object that is a defined in an enclosing scope, - * - any object that is a member of an enclosing class, - * - any enclosing package (including the root package), - * - any object that is a member of a searched object or package, - * - any object or package from which something is imported in an enclosing scope, - * - any package that is nested in a searched package, provided - * the package was accessed in some way previously. - */ - class suggestions(qualifies: TermRef => Boolean) with - private val seen = mutable.Set[TermRef]() - - private def lookInside(root: Symbol)(given Context): Boolean = - if root.is(Package) then root.isTerm && root.isCompleted - else !root.name.is(FlatName) - && !root.name.lastPart.contains('$') - && root.is(ModuleVal, butNot = JavaDefined) - - def nestedRoots(site: Type)(given Context): List[Symbol] = - val seenNames = mutable.Set[Name]() - site.baseClasses.flatMap { bc => - bc.info.decls.filter { dcl => - lookInside(dcl) - && !seenNames.contains(dcl.name) - && { seenNames += dcl.name; true } - } - } - - private def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] = - val site = ref.widen - val refSym = site.typeSymbol - val nested = - if refSym.is(Package) then - if refSym == defn.EmptyPackageClass // Don't search the empty package - || refSym == defn.JavaPackageClass // As an optimization, don't search java... - || refSym == defn.JavaLangPackageClass // ... or java.lang. - then Nil - else refSym.info.decls.filter(lookInside) - else - if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion - if refSym.is(JavaDefined) then Nil - else nestedRoots(site) - nested - .map(mbr => TermRef(ref, mbr.asTerm)) - .flatMap(rootsIn) - .toList - - private def rootsIn(ref: TermRef)(given Context): List[TermRef] = - if seen.contains(ref) then Nil - else - implicits.println(i"search for suggestions in ${ref.symbol.fullName}") - seen += ref - ref :: rootsStrictlyIn(ref) - - private def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match - case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) - case _ => Nil - - private def roots(given ctx: Context): List[TermRef] = - if ctx.owner.exists then - val defined = - if ctx.owner.isClass then - if ctx.owner eq ctx.outer.owner then Nil - else rootsStrictlyIn(ctx.owner.thisType) - else - if ctx.scope eq ctx.outer.scope then Nil - else ctx.scope - .filter(lookInside(_)) - .flatMap(sym => rootsIn(sym.termRef)) - val imported = - if ctx.importInfo eq ctx.outer.importInfo then Nil - else ctx.importInfo.sym.info match - case ImportType(expr) => rootsOnPath(expr.tpe) - case _ => Nil - defined ++ imported ++ roots(given ctx.outer) - else Nil - - def search(given ctx: Context): List[TermRef] = - val timer = new Timer() - val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut - - def test(ref: TermRef)(given Context): Boolean = - System.currentTimeMillis < deadLine - && { - val task = new TimerTask with - def run() = - implicits.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") - ctx.run.isCancelled = true - timer.schedule(task, testOneImplicitTimeOut) - try qualifies(ref) - finally - task.cancel() - ctx.run.isCancelled = false - } - - try - roots - .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) - // don't suggest things that are imported by default - .flatMap(_.implicitMembers.filter(test)) - catch - case ex: Throwable => - if ctx.settings.Ydebug.value then - println("caught exception when searching for suggestions") - ex.printStackTrace() - Nil - finally timer.cancel() - end search - end suggestions } import Implicits._ @@ -810,20 +695,174 @@ trait Implicits { self: Typer => } } + /** A helper class to find imports of givens that might fix a type error. + * + * suggestions(p).search + * + * returns a list of TermRefs that refer to implicits or givens + * that satisfy predicate `p`. + * + * The search algorithm looks for givens in the smallest set of objects + * and packages that includes + * + * - any object that is a defined in an enclosing scope, + * - any object that is a member of an enclosing class, + * - any enclosing package (including the root package), + * - any object that is a member of a searched object or package, + * - any object or package from which something is imported in an enclosing scope, + * - any package that is nested in a searched package, provided + * the package was accessed in some way previously. + */ + private class Suggestions(pt: Type) with + private val seen = mutable.Set[TermRef]() + + private def lookInside(root: Symbol)(given Context): Boolean = + if root.is(Package) then root.isTerm && root.isCompleted + else !root.name.is(FlatName) + && !root.name.lastPart.contains('$') + && root.is(ModuleVal, butNot = JavaDefined) + + def nestedRoots(site: Type)(given Context): List[Symbol] = + val seenNames = mutable.Set[Name]() + site.baseClasses.flatMap { bc => + bc.info.decls.filter { dcl => + lookInside(dcl) + && !seenNames.contains(dcl.name) + && { seenNames += dcl.name; true } + } + } + + private def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] = + val site = ref.widen + val refSym = site.typeSymbol + val nested = + if refSym.is(Package) then + if refSym == defn.EmptyPackageClass // Don't search the empty package + || refSym == defn.JavaPackageClass // As an optimization, don't search java... + || refSym == defn.JavaLangPackageClass // ... or java.lang. + then Nil + else refSym.info.decls.filter(lookInside) + else + if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion + if refSym.is(JavaDefined) then Nil + else nestedRoots(site) + nested + .map(mbr => TermRef(ref, mbr.asTerm)) + .flatMap(rootsIn) + .toList + + private def rootsIn(ref: TermRef)(given Context): List[TermRef] = + if seen.contains(ref) then Nil + else + implicits.println(i"search for suggestions in ${ref.symbol.fullName}") + seen += ref + ref :: rootsStrictlyIn(ref) + + private def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match + case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) + case _ => Nil + + private def roots(given ctx: Context): List[TermRef] = + if ctx.owner.exists then + val defined = + if ctx.owner.isClass then + if ctx.owner eq ctx.outer.owner then Nil + else rootsStrictlyIn(ctx.owner.thisType) + else + if ctx.scope eq ctx.outer.scope then Nil + else ctx.scope + .filter(lookInside(_)) + .flatMap(sym => rootsIn(sym.termRef)) + val imported = + if ctx.importInfo eq ctx.outer.importInfo then Nil + else ctx.importInfo.sym.info match + case ImportType(expr) => rootsOnPath(expr.tpe) + case _ => Nil + defined ++ imported ++ roots(given ctx.outer) + else Nil + + def search(given ctx: Context): (List[TermRef], List[TermRef]) = + val timer = new Timer() + val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut + + /** Test whether the head of a given instance matches the expected type `pt`, + * ignoring any dependent implicit arguments. + */ + def shallowTest(ref: TermRef)(given Context): Boolean = + System.currentTimeMillis < deadLine + && (ref <:< pt)(given ctx.fresh.setExploreTyperState()) + + /** Test whether a full given term can be synthesized that matches + * the expected type `pt`. + */ + def deepTest(ref: TermRef)(given Context): Boolean = + System.currentTimeMillis < deadLine + && { + val task = new TimerTask with + def run() = + println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + ctx.run.isCancelled = true + val span = ctx.owner.sourcePos.span + val (expectedType, argument, kind) = pt match + case ViewProto(argType, resType) => + (resType, + untpd.Ident(ref.name).withSpan(span).withType(argType), + if hasExtMethod(ref, resType) then Candidate.Extension + else Candidate.Conversion) + case _ => + (pt, EmptyTree, Candidate.Value) + val candidate = Candidate(ref, kind, 0) + try + timer.schedule(task, testOneImplicitTimeOut) + typedImplicit(candidate, expectedType, argument, span)( + given ctx.fresh.setExploreTyperState()).isSuccess + finally + task.cancel() + ctx.run.isCancelled = false + } + end deepTest + + try + roots + .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) + // don't suggest things that are imported by default + .flatMap(_.implicitMembers.filter(shallowTest)) + // filter whether the head of the implicit can match + .partition(deepTest) + // partition into full matches and head matches + catch + case ex: Throwable => + if ctx.settings.Ydebug.value then + println("caught exception when searching for suggestions") + ex.printStackTrace() + (Nil, Nil) + finally timer.cancel() + end search + end Suggestions + /** An addendum to an error message where the error might be fixed * by some implicit value of type `pt` that is however not found. * The addendum suggests given imports that might fix the problem. * If there's nothing to suggest, an empty string is returned. */ override def implicitSuggestionsFor(pt: Type)(given ctx: Context): String = - val suggestedRefs = - Implicits.suggestions(_ <:< pt).search(given ctx.fresh.setExploreTyperState()) + val (fullMatches, headMatches) = + Suggestions(pt).search(given ctx.fresh.setExploreTyperState()) + implicits.println(i"suggestions for $pt in ${ctx.owner} = ($fullMatches%, %, $headMatches%, %)") + val (suggestedRefs, help) = + if fullMatches.nonEmpty then (fullMatches, "fix") + else (headMatches, "make progress towards fixing") def importString(ref: TermRef): String = s" import ${ctx.printer.toTextRef(ref).show}" - val suggestions = suggestedRefs.map(importString) - .filter(_.contains('.')) + val suggestions = suggestedRefs + .zip(suggestedRefs.map(importString)) + .filter(_._2.contains('.')) + .sortWith { (rs1, rs2) => // sort by specificity first, alphabetically second + val diff = compare(rs1._1, rs2._1) + diff > 0 || diff == 0 && rs1._2 < rs2._2 + } + .map(_._2) .distinct // TermRefs might be different but generate the same strings - .sorted // To get test stability. TODO: Find more useful sorting criteria if suggestions.isEmpty then "" else val fix = @@ -831,7 +870,7 @@ trait Implicits { self: Typer => else "One of the following imports" i""" | - |$fix might fix the problem: + |$fix might $help the problem: | |$suggestions%\n% """ @@ -1511,33 +1550,10 @@ trait Implicits { self: Typer => ctx.searchHistory.emitDictionary(span, result) } - /** An implicit search; parameters as in `inferImplicit` */ - class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(implicit ctx: Context) { - assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], - em"found: $argument: ${argument.tpe}, expected: $pt") - - private def nestedContext() = - ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) - - private def implicitProto(resultType: Type, f: Type => Type) = - if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) - // Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though. - - private def isCoherent = pt.isRef(defn.EqlClass) - - /** The expected type for the searched implicit */ - @threadUnsafe lazy val fullProto: Type = implicitProto(pt, identity) - - /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ - val wildProto: Type = implicitProto(pt, wildApprox(_)) - - val isNot: Boolean = wildProto.classSymbol == defn.NotClass - - //println(i"search implicits $pt / ${eligible.map(_.ref)}") - - /** Try to typecheck an implicit reference */ - 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) { - if ctx.run.isCancelled then return NoMatchingImplicitsFailure + /** Try to typecheck an implicit reference */ + 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) { + if ctx.run.isCancelled then NoMatchingImplicitsFailure + else record("typedImplicit") val ref = cand.ref val generated: Tree = tpd.ref(ref).withSpan(span.startPos) @@ -1591,6 +1607,30 @@ trait Implicits { self: Typer => } } + /** An implicit search; parameters as in `inferImplicit` */ + class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(implicit ctx: Context) { + assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], + em"found: $argument: ${argument.tpe}, expected: $pt") + + private def nestedContext() = + ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) + + private def implicitProto(resultType: Type, f: Type => Type) = + if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) + // Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though. + + private def isCoherent = pt.isRef(defn.EqlClass) + + /** The expected type for the searched implicit */ + @threadUnsafe lazy val fullProto: Type = implicitProto(pt, identity) + + /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ + val wildProto: Type = implicitProto(pt, wildApprox(_)) + + val isNot: Boolean = wildProto.classSymbol == defn.NotClass + + //println(i"search implicits $pt / ${eligible.map(_.ref)}") + /** Try to type-check implicit reference, after checking that this is not * a diverging search */ @@ -1600,7 +1640,7 @@ trait Implicits { self: Typer => else { val history = ctx.searchHistory.nest(cand, pt) val result = - typedImplicit(cand, contextual)(nestedContext().setNewTyperState().setFreshGADTBounds.setSearchHistory(history)) + typedImplicit(cand, pt, argument, span)(nestedContext().setNewTyperState().setFreshGADTBounds.setSearchHistory(history)) result match { case res: SearchSuccess => ctx.searchHistory.defineBynameImplicit(pt.widenExpr, res) diff --git a/tests/neg/missing-implicit.check b/tests/neg/missing-implicit.check index 3159c16597d6..8ff317a56ac5 100644 --- a/tests/neg/missing-implicit.check +++ b/tests/neg/missing-implicit.check @@ -13,10 +13,8 @@ | ^^^^^^^ | value - is not a member of T | - | One of the following imports might fix the problem: + | The following import might fix the problem: | - | import math.Fractional.Implicits.infixFractionalOps - | import math.Integral.Implicits.infixIntegralOps | import math.Numeric.Implicits.infixNumericOps | -- Error: tests/neg/missing-implicit.scala:10:24 ----------------------------------------------------------------------- @@ -46,14 +44,14 @@ | Required: Byte² | | where: Byte is a class in package scala - | Byte² is a class in package lang + | Byte² is a class in package java.lang -- [E007] Type Mismatch Error: tests/neg/missing-implicit.scala:16:44 -------------------------------------------------- 16 |val d: scala.concurrent.duration.Duration = (10, DAYS) // error | ^^^^^^^^^^ | Found: (Int, java.util.concurrent.TimeUnit) | Required: concurrent².duration.Duration | - | where: concurrent is a package in package util + | where: concurrent is a package in package java.util | concurrent² is a package in package scala | | @@ -61,3 +59,14 @@ | | import concurrent.duration.pairIntToDuration | +-- [E008] Member Not Found Error: tests/neg/missing-implicit.scala:18:48 ----------------------------------------------- +18 |val d2: scala.concurrent.duration.Duration = 10.days // error + | ^^^^^^^ + | value days is not a member of Int + | + | One of the following imports might fix the problem: + | + | import concurrent.duration.DurationInt + | import concurrent.duration.DurationLong + | import concurrent.duration.DurationDouble + | diff --git a/tests/neg/missing-implicit.scala b/tests/neg/missing-implicit.scala index 3e786f28aa27..34d88f0e74eb 100644 --- a/tests/neg/missing-implicit.scala +++ b/tests/neg/missing-implicit.scala @@ -14,3 +14,5 @@ val b: java.lang.Byte = (1: Byte) // error, but no hint val DAYS = scala.concurrent.duration.DAYS val d: scala.concurrent.duration.Duration = (10, DAYS) // error + +val d2: scala.concurrent.duration.Duration = 10.days // error diff --git a/tests/neg/missing-implicit1.check b/tests/neg/missing-implicit1.check index b267d5d4d7a3..491f2cc79bb7 100644 --- a/tests/neg/missing-implicit1.check +++ b/tests/neg/missing-implicit1.check @@ -1,5 +1,5 @@ --- Error: tests/neg/missing-implicit1.scala:16:4 ----------------------------------------------------------------------- -16 | ff // error +-- Error: tests/neg/missing-implicit1.scala:14:4 ----------------------------------------------------------------------- +14 | ff // error | ^ |no implicit argument of type testObjectInstance.Zip[Option] was found for parameter xs of method ff in object testObjectInstance | @@ -7,17 +7,26 @@ | | import testObjectInstance.instances.zipOption | --- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:18:16 ---------------------------------------------- -18 | List(1, 2, 3).traverse(x => Option(x)) // error +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:16:16 ---------------------------------------------- +16 | List(1, 2, 3).traverse(x => Option(x)) // error | ^^^^^^^^^^^^^^^^^^^^^^ | value traverse is not a member of List[Int] - did you mean List[Int].reverse? | - | The following import might fix the problem: + | The following import might make progress towards fixing the problem: | | import testObjectInstance.instances.traverseList | --- Error: tests/neg/missing-implicit1.scala:35:4 ----------------------------------------------------------------------- -35 | ff // error +-- Error: tests/neg/missing-implicit1.scala:20:42 ---------------------------------------------------------------------- +20 | List(1, 2, 3).traverse(x => Option(x)) // error + | ^ + |no implicit argument of type testObjectInstance.Zip[Option] was found for parameter evidence$1 of method traverse in trait Traverse + | + |The following import might fix the problem: + | + | import testObjectInstance.instances.zipOption + | +-- Error: tests/neg/missing-implicit1.scala:36:4 ----------------------------------------------------------------------- +36 | ff // error | ^ | no implicit argument of type Zip[Option] was found for parameter xs of method ff | @@ -25,12 +34,21 @@ | | import instances.zipOption | --- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:37:16 ---------------------------------------------- -37 | List(1, 2, 3).traverse(x => Option(x)) // error +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:38:16 ---------------------------------------------- +38 | List(1, 2, 3).traverse(x => Option(x)) // error | ^^^^^^^^^^^^^^^^^^^^^^ | value traverse is not a member of List[Int] - did you mean List[Int].reverse? | - | The following import might fix the problem: + | The following import might make progress towards fixing the problem: | | import instances.traverseList | +-- Error: tests/neg/missing-implicit1.scala:42:42 ---------------------------------------------------------------------- +42 | List(1, 2, 3).traverse(x => Option(x)) // error + | ^ + |no implicit argument of type Zip[Option] was found for parameter evidence$2 of method traverse in trait Traverse + | + |The following import might fix the problem: + | + | import instances.zipOption + | diff --git a/tests/neg/missing-implicit1.scala b/tests/neg/missing-implicit1.scala index bb74741bb2e4..87f4c5f6b091 100644 --- a/tests/neg/missing-implicit1.scala +++ b/tests/neg/missing-implicit1.scala @@ -11,12 +11,15 @@ object testObjectInstance with def ff(given xs: Zip[Option]) = ??? - //import instances.zipOption - ff // error List(1, 2, 3).traverse(x => Option(x)) // error + locally { + import instances.traverseList + List(1, 2, 3).traverse(x => Option(x)) // error + } + def testLocalInstance = trait Zip[F[_]] trait Traverse[F[_]] { @@ -30,8 +33,11 @@ def testLocalInstance = def ff(given xs: Zip[Option]) = ??? - //import instances.zipOption - ff // error List(1, 2, 3).traverse(x => Option(x)) // error + + locally { + import instances.traverseList + List(1, 2, 3).traverse(x => Option(x)) // error + } From ad5055b5d3d13a4a673c1dfc8ced87727473f1be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 4 Jan 2020 18:38:03 +0100 Subject: [PATCH 15/27] Further refinement of "did you mean" hints --- .../src/dotty/tools/dotc/reporting/diagnostic/messages.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index bb56d91aa960..c7d3e4aea27a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -347,7 +347,9 @@ object messages { decls .map { (n, sym) => (n, distance(n, name.show), sym) } .collect { - case (n, dist, sym) if dist <= maxDist && dist < name.toString.length => (n, dist, sym) + case (n, dist, sym) + if dist <= maxDist && dist < (name.toString.length min n.length) => + (n, dist, sym) } .groupBy(_._2).toList .sortBy(_._1) From 848dd0a6a2a41c4a7b374af05c7fc1761ff6e9fd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 4 Jan 2020 18:38:32 +0100 Subject: [PATCH 16/27] Print package symbols always under their full names --- compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala | 1 + compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 ++ 2 files changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index e18964bb0e15..0f5ba5d027d6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -438,6 +438,7 @@ class PlainPrinter(_ctx: Context) extends Printer { (kindString(sym) ~~ { if (sym.isAnonymousClass) toTextParents(sym.info.parents) ~~ "{...}" else if (hasMeaninglessName(sym) && !printDebug) simpleNameString(sym.owner) + idString(sym) + else if sym.is(Package) then fullNameString(sym) else nameString(sym) }).close diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 83c0912a325e..e5ed7031b071 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -900,6 +900,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def name = if (printDebug) nameString(sym) + else if sym.is(Package) then + fullNameString(sym) else if (sym.is(ModuleClass) && sym.isPackageObject && sym.name.stripModuleClassSuffix == tpnme.PACKAGE) nameString(sym.owner.name) else if (sym.is(ModuleClass)) From 72df855d4ff51e168b552df608f889175a1d833d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 4 Jan 2020 19:18:33 +0100 Subject: [PATCH 17/27] Fix repl test --- compiler/test-resources/repl/i6676 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test-resources/repl/i6676 b/compiler/test-resources/repl/i6676 index f543c6ae2de3..627f9d232b23 100644 --- a/compiler/test-resources/repl/i6676 +++ b/compiler/test-resources/repl/i6676 @@ -8,7 +8,7 @@ scala> xml" scala> xml"" 1 | xml"" | ^ - |value xml is not a member of StringContext - did you mean StringContext.s? + |value xml is not a member of StringContext scala> xml""" 1 | xml""" | ^ From 0501bfa12c107009b2f91dcb19f9057d1610aa67 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 4 Jan 2020 19:20:01 +0100 Subject: [PATCH 18/27] Refactorings and comments for better clarity --- .../tools/dotc/typer/ErrorReporting.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 169 ++++++++++-------- .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 +- 3 files changed, 96 insertions(+), 79 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 9a1941bcc982..c0ef5217b9cc 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -157,7 +157,7 @@ object ErrorReporting { || expected.isRef(defn.ObjectClass) || defn.isBottomType(found) then postScript - else ctx.typer.implicitSuggestionsFor(ViewProto(found.widen, expected)) + else ctx.typer.implicitSuggestionAddendum(ViewProto(found.widen, expected)) TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript1) } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index e0006bd1e62a..b1b1ee66cb92 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -695,15 +695,10 @@ trait Implicits { self: Typer => } } - /** A helper class to find imports of givens that might fix a type error. + /** A list of TermRefs referring to the roots where suggestions for + * imports of givens that might fix a type error are searched. * - * suggestions(p).search - * - * returns a list of TermRefs that refer to implicits or givens - * that satisfy predicate `p`. - * - * The search algorithm looks for givens in the smallest set of objects - * and packages that includes + * These roots are the smallest set of objects and packages that includes * * - any object that is a defined in an enclosing scope, * - any object that is a member of an enclosing class, @@ -712,11 +707,23 @@ trait Implicits { self: Typer => * - any object or package from which something is imported in an enclosing scope, * - any package that is nested in a searched package, provided * the package was accessed in some way previously. + * + * Excluded from the root set are: + * + * - Objects that contain `$`s in their name. These have to + * be omitted since they might be inner Java class files which + * cannot be read by the ClassfileParser without crashing. + * - Any members of static parts of Java classes. + * - Any members of the empty package. These should be + * skipped since the empty package often contains unrelated junk files + * that should not be used for suggestions. + * - Any members of the java or java.lang packages. These are + * skipped as an optimization, since they won't contain implicits anyway. */ - private class Suggestions(pt: Type) with - private val seen = mutable.Set[TermRef]() + private def suggestionRoots(given Context) = + val seen = mutable.Set[TermRef]() - private def lookInside(root: Symbol)(given Context): Boolean = + def lookInside(root: Symbol)(given Context): Boolean = if root.is(Package) then root.isTerm && root.isCompleted else !root.name.is(FlatName) && !root.name.lastPart.contains('$') @@ -732,7 +739,7 @@ trait Implicits { self: Typer => } } - private def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] = + def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] = val site = ref.widen val refSym = site.typeSymbol val nested = @@ -751,18 +758,18 @@ trait Implicits { self: Typer => .flatMap(rootsIn) .toList - private def rootsIn(ref: TermRef)(given Context): List[TermRef] = + def rootsIn(ref: TermRef)(given Context): List[TermRef] = if seen.contains(ref) then Nil else implicits.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref ref :: rootsStrictlyIn(ref) - private def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match + def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) case _ => Nil - private def roots(given ctx: Context): List[TermRef] = + def recur(given ctx: Context): List[TermRef] = if ctx.owner.exists then val defined = if ctx.owner.isClass then @@ -778,76 +785,86 @@ trait Implicits { self: Typer => else ctx.importInfo.sym.info match case ImportType(expr) => rootsOnPath(expr.tpe) case _ => Nil - defined ++ imported ++ roots(given ctx.outer) + defined ++ imported ++ recur(given ctx.outer) else Nil - def search(given ctx: Context): (List[TermRef], List[TermRef]) = - val timer = new Timer() - val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut + recur + end suggestionRoots - /** Test whether the head of a given instance matches the expected type `pt`, - * ignoring any dependent implicit arguments. - */ - def shallowTest(ref: TermRef)(given Context): Boolean = - System.currentTimeMillis < deadLine - && (ref <:< pt)(given ctx.fresh.setExploreTyperState()) + /** Given an expected type `pt`, return two lists of TermRefs: + * + * 1. The _fully matching_ given instances that can be completed + * to a full synthesized given term that matches the expected type `pt`. + * + * 2. The _head matching_ given instances, that conform to the + * expected type `pt`, ignoring any dependent implicit arguments. + */ + private def importSuggestions(pt: Type)(given ctx: Context): (List[TermRef], List[TermRef]) = + val timer = new Timer() + val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut - /** Test whether a full given term can be synthesized that matches - * the expected type `pt`. - */ - def deepTest(ref: TermRef)(given Context): Boolean = - System.currentTimeMillis < deadLine - && { - val task = new TimerTask with - def run() = - println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") - ctx.run.isCancelled = true - val span = ctx.owner.sourcePos.span - val (expectedType, argument, kind) = pt match - case ViewProto(argType, resType) => - (resType, - untpd.Ident(ref.name).withSpan(span).withType(argType), - if hasExtMethod(ref, resType) then Candidate.Extension - else Candidate.Conversion) - case _ => - (pt, EmptyTree, Candidate.Value) - val candidate = Candidate(ref, kind, 0) - try - timer.schedule(task, testOneImplicitTimeOut) - typedImplicit(candidate, expectedType, argument, span)( - given ctx.fresh.setExploreTyperState()).isSuccess - finally - task.cancel() - ctx.run.isCancelled = false - } - end deepTest - - try - roots - .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) - // don't suggest things that are imported by default - .flatMap(_.implicitMembers.filter(shallowTest)) - // filter whether the head of the implicit can match - .partition(deepTest) - // partition into full matches and head matches - catch - case ex: Throwable => - if ctx.settings.Ydebug.value then - println("caught exception when searching for suggestions") - ex.printStackTrace() - (Nil, Nil) - finally timer.cancel() - end search - end Suggestions + /** Test whether the head of a given instance matches the expected type `pt`, + * ignoring any dependent implicit arguments. + */ + def shallowTest(ref: TermRef)(given Context): Boolean = + System.currentTimeMillis < deadLine + && (ref <:< pt)(given ctx.fresh.setExploreTyperState()) + + /** Test whether a full given term can be synthesized that matches + * the expected type `pt`. + */ + def deepTest(ref: TermRef)(given Context): Boolean = + System.currentTimeMillis < deadLine + && { + val task = new TimerTask with + def run() = + println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + ctx.run.isCancelled = true + val span = ctx.owner.sourcePos.span + val (expectedType, argument, kind) = pt match + case ViewProto(argType, resType) => + (resType, + untpd.Ident(ref.name).withSpan(span).withType(argType), + if hasExtMethod(ref, resType) then Candidate.Extension + else Candidate.Conversion) + case _ => + (pt, EmptyTree, Candidate.Value) + val candidate = Candidate(ref, kind, 0) + try + timer.schedule(task, testOneImplicitTimeOut) + typedImplicit(candidate, expectedType, argument, span)( + given ctx.fresh.setExploreTyperState()).isSuccess + finally + task.cancel() + ctx.run.isCancelled = false + } + end deepTest + + try + suggestionRoots + .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) + // don't suggest things that are imported by default + .flatMap(_.implicitMembers.filter(shallowTest)) + // filter whether the head of the implicit can match + .partition(deepTest) + // partition into full matches and head matches + catch + case ex: Throwable => + if ctx.settings.Ydebug.value then + println("caught exception when searching for suggestions") + ex.printStackTrace() + (Nil, Nil) + finally timer.cancel() + end importSuggestions /** An addendum to an error message where the error might be fixed * by some implicit value of type `pt` that is however not found. * The addendum suggests given imports that might fix the problem. * If there's nothing to suggest, an empty string is returned. */ - override def implicitSuggestionsFor(pt: Type)(given ctx: Context): String = + override def implicitSuggestionAddendum(pt: Type)(given ctx: Context): String = val (fullMatches, headMatches) = - Suggestions(pt).search(given ctx.fresh.setExploreTyperState()) + importSuggestions(pt)(given ctx.fresh.setExploreTyperState()) implicits.println(i"suggestions for $pt in ${ctx.owner} = ($fullMatches%, %, $headMatches%, %)") val (suggestedRefs, help) = if fullMatches.nonEmpty then (fullMatches, "fix") @@ -874,7 +891,7 @@ trait Implicits { self: Typer => | |$suggestions%\n% """ - end implicitSuggestionsFor + end implicitSuggestionAddendum /** Handlers to synthesize implicits for special types */ type SpecialHandler = (Type, Span) => Context => Tree @@ -1435,7 +1452,7 @@ trait Implicits { self: Typer => // example where searching for a nested type causes an infinite loop. "" - def suggestedImports = implicitSuggestionsFor(pt) + def suggestedImports = implicitSuggestionAddendum(pt) if normalImports.isEmpty then suggestedImports else normalImports end hiddenImplicitsAddendum diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index a4e9c22f78aa..cc42969f7e5c 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -283,7 +283,7 @@ trait TypeAssigner { |If you do not want that, insert a `;` or empty line in front |or drop any spaces behind the operator.""" else - implicitSuggestionsFor( + implicitSuggestionAddendum( ViewProto(qualType.widen, SelectionProto(name, WildcardType, NoViewsAllowed, privateOK = false))) } @@ -291,7 +291,7 @@ trait TypeAssigner { } } - def implicitSuggestionsFor(pt: Type)(given Context): String = "" + def implicitSuggestionAddendum(pt: Type)(given Context): String = "" /** The type of the selection in `tree`, where `qual1` is the typed qualifier part. * The selection type is additionally checked for accessibility. From 1124b352ded20dafa54d424f0159157a14ca64ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 4 Jan 2020 22:46:14 +0100 Subject: [PATCH 19/27] Fix two more repl tests --- compiler/test-resources/repl/1379 | 2 +- compiler/test-resources/repl/i6676 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/test-resources/repl/1379 b/compiler/test-resources/repl/1379 index 241b57166b76..902d6251a62b 100644 --- a/compiler/test-resources/repl/1379 +++ b/compiler/test-resources/repl/1379 @@ -1,4 +1,4 @@ scala> object Foo { val bar = new Object { def baz = 1 }; bar.baz } 1 | object Foo { val bar = new Object { def baz = 1 }; bar.baz } | ^^^^^^^ - | value baz is not a member of Object - did you mean bar.eq? \ No newline at end of file + | value baz is not a member of Object \ No newline at end of file diff --git a/compiler/test-resources/repl/i6676 b/compiler/test-resources/repl/i6676 index 627f9d232b23..9fc239ca9855 100644 --- a/compiler/test-resources/repl/i6676 +++ b/compiler/test-resources/repl/i6676 @@ -8,7 +8,7 @@ scala> xml" scala> xml"" 1 | xml"" | ^ - |value xml is not a member of StringContext + | value xml is not a member of StringContext scala> xml""" 1 | xml""" | ^ From c601a72d7014cb00bfc41d0081ee3f661c6f6f84 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 10:19:07 +0100 Subject: [PATCH 20/27] Issue a "did you mean" hint only if there's nothing else to say --- .../dotc/reporting/diagnostic/messages.scala | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index c7d3e4aea27a..680bf63d3351 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -343,7 +343,7 @@ object messages { } // Get closest match in `site` - val closest = + def closest = decls .map { (n, sym) => (n, distance(n, name.show), sym) } .collect { @@ -358,22 +358,22 @@ object messages { .sortBy(_._3) .take(1).map { case (n, sym, _) => (n, sym) } - val siteName = site match { - case site: NamedType => site.name.show - case site => i"$site" - } - - val closeMember = closest match { - case (n, sym) :: Nil => - s" - did you mean $siteName.$n?" - case Nil => "" - case _ => assert( - false, - "Could not single out one distinct member to match on input with" - ) - } + val finalAddendum = + if addendum.nonEmpty then addendum + else closest match { + case (n, _) :: Nil => + val siteName = site match + case site: NamedType => site.name.show + case site => i"$site" + s" - did you mean $siteName.$n?" + case Nil => "" + case _ => assert( + false, + "Could not single out one distinct member to match on input with" + ) + } - ex"$selected $name is not a member of ${site.widen}$closeMember$addendum" + ex"$selected $name is not a member of ${site.widen}$finalAddendum" } val explanation: String = "" From 02d75945d0667e84363f794f383e3948b60b9800 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 10:32:49 +0100 Subject: [PATCH 21/27] Remove dead code and data in "did you mean" hints --- .../tools/dotc/reporting/diagnostic/messages.scala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 680bf63d3351..ae360fbd9a0f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -343,7 +343,7 @@ object messages { } // Get closest match in `site` - def closest = + def closest: List[String] = decls .map { (n, sym) => (n, distance(n, name.show), sym) } .collect { @@ -356,21 +356,19 @@ object messages { .headOption.map(_._2).getOrElse(Nil) .map(incorrectChars).toList .sortBy(_._3) - .take(1).map { case (n, sym, _) => (n, sym) } + .map(_._1) + // [Martin] Note: I have no idea what this does. This shows the + // pitfalls of not naming things, functional or not. val finalAddendum = if addendum.nonEmpty then addendum else closest match { - case (n, _) :: Nil => + case n :: _ => val siteName = site match case site: NamedType => site.name.show case site => i"$site" s" - did you mean $siteName.$n?" case Nil => "" - case _ => assert( - false, - "Could not single out one distinct member to match on input with" - ) } ex"$selected $name is not a member of ${site.widen}$finalAddendum" From 9e72e6cbe89923eb8e807436c2980e4cdee3a6c5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 11:44:34 +0100 Subject: [PATCH 22/27] Better explanation for missing members that have extension methods --- .../dotty/tools/dotc/typer/TypeAssigner.scala | 8 ++++--- tests/neg/missing-implicit.check | 22 +++++++++---------- tests/neg/missing-implicit1.check | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index cc42969f7e5c..e604f8f1b1c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -270,7 +270,7 @@ trait TypeAssigner { def addendum = if (qualType.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" - else qual1.getAttachment(Typer.HiddenSearchFailure) match { + else qual1.getAttachment(Typer.HiddenSearchFailure) match case Some(failure) if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] => i""". |An extension method was tried, but could not be fully constructed: @@ -283,10 +283,12 @@ trait TypeAssigner { |If you do not want that, insert a `;` or empty line in front |or drop any spaces behind the operator.""" else - implicitSuggestionAddendum( + var add = implicitSuggestionAddendum( ViewProto(qualType.widen, SelectionProto(name, WildcardType, NoViewsAllowed, privateOK = false))) - } + if add.isEmpty then "" + else ", but could be made available as an extension method." ++ add + end addendum errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos) } } diff --git a/tests/neg/missing-implicit.check b/tests/neg/missing-implicit.check index 8ff317a56ac5..8ac23fee4918 100644 --- a/tests/neg/missing-implicit.check +++ b/tests/neg/missing-implicit.check @@ -1,7 +1,7 @@ -- [E008] Member Not Found Error: tests/neg/missing-implicit.scala:5:25 ------------------------------------------------ 5 | case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error | ^^^^^^^ - | value > is not a member of T + | value > is not a member of T, but could be made available as an extension method. | | One of the following imports might fix the problem: | @@ -11,12 +11,12 @@ -- [E008] Member Not Found Error: tests/neg/missing-implicit.scala:5:51 ------------------------------------------------ 5 | case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error | ^^^^^^^ - | value - is not a member of T + | value - is not a member of T, but could be made available as an extension method. | - | The following import might fix the problem: + | The following import might fix the problem: | - | import math.Numeric.Implicits.infixNumericOps - | + | import math.Numeric.Implicits.infixNumericOps + | -- Error: tests/neg/missing-implicit.scala:10:24 ----------------------------------------------------------------------- 10 |val f = Future[Unit] { } // error | ^ @@ -62,11 +62,11 @@ -- [E008] Member Not Found Error: tests/neg/missing-implicit.scala:18:48 ----------------------------------------------- 18 |val d2: scala.concurrent.duration.Duration = 10.days // error | ^^^^^^^ - | value days is not a member of Int + | value days is not a member of Int, but could be made available as an extension method. | - | One of the following imports might fix the problem: + | One of the following imports might fix the problem: | - | import concurrent.duration.DurationInt - | import concurrent.duration.DurationLong - | import concurrent.duration.DurationDouble - | + | import concurrent.duration.DurationInt + | import concurrent.duration.DurationLong + | import concurrent.duration.DurationDouble + | diff --git a/tests/neg/missing-implicit1.check b/tests/neg/missing-implicit1.check index 491f2cc79bb7..d2b90329f539 100644 --- a/tests/neg/missing-implicit1.check +++ b/tests/neg/missing-implicit1.check @@ -10,7 +10,7 @@ -- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:16:16 ---------------------------------------------- 16 | List(1, 2, 3).traverse(x => Option(x)) // error | ^^^^^^^^^^^^^^^^^^^^^^ - | value traverse is not a member of List[Int] - did you mean List[Int].reverse? + | value traverse is not a member of List[Int], but could be made available as an extension method. | | The following import might make progress towards fixing the problem: | @@ -37,7 +37,7 @@ -- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:38:16 ---------------------------------------------- 38 | List(1, 2, 3).traverse(x => Option(x)) // error | ^^^^^^^^^^^^^^^^^^^^^^ - | value traverse is not a member of List[Int] - did you mean List[Int].reverse? + | value traverse is not a member of List[Int], but could be made available as an extension method. | | The following import might make progress towards fixing the problem: | From aabcd8194381883f410473f095edd870db9d8a3f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 13:21:31 +0100 Subject: [PATCH 23/27] Also suggest imports of extension methods Also suggest direct imports of extension methods that are not members of a given instance. --- .../tools/dotc/typer/ErrorReporting.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 55 +++++++++++++++---- .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 +- tests/neg/missing-implicit1.check | 46 ++++++++++++---- tests/neg/missing-implicit1.scala | 9 +++ 5 files changed, 89 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index c0ef5217b9cc..f83159c69b6d 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -157,7 +157,7 @@ object ErrorReporting { || expected.isRef(defn.ObjectClass) || defn.isBottomType(found) then postScript - else ctx.typer.implicitSuggestionAddendum(ViewProto(found.widen, expected)) + else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected)) TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript1) } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index b1b1ee66cb92..f82bbf866ace 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -696,7 +696,8 @@ trait Implicits { self: Typer => } /** A list of TermRefs referring to the roots where suggestions for - * imports of givens that might fix a type error are searched. + * imports of givens or extension methods that might fix a type error + * are searched. * * These roots are the smallest set of objects and packages that includes * @@ -798,6 +799,11 @@ trait Implicits { self: Typer => * * 2. The _head matching_ given instances, that conform to the * expected type `pt`, ignoring any dependent implicit arguments. + * + * If there are no fully matching given instances under (1), and `pt` is + * a view prototype of a selection of the form `T ?=>? { name: ... }`, + * return instead a list of all possible references to extension methods named + * `name` that are applicable to `T`. */ private def importSuggestions(pt: Type)(given ctx: Context): (List[TermRef], List[TermRef]) = val timer = new Timer() @@ -806,14 +812,14 @@ trait Implicits { self: Typer => /** Test whether the head of a given instance matches the expected type `pt`, * ignoring any dependent implicit arguments. */ - def shallowTest(ref: TermRef)(given Context): Boolean = + def shallowTest(ref: TermRef): Boolean = System.currentTimeMillis < deadLine && (ref <:< pt)(given ctx.fresh.setExploreTyperState()) /** Test whether a full given term can be synthesized that matches * the expected type `pt`. */ - def deepTest(ref: TermRef)(given Context): Boolean = + def deepTest(ref: TermRef): Boolean = System.currentTimeMillis < deadLine && { val task = new TimerTask with @@ -840,14 +846,37 @@ trait Implicits { self: Typer => } end deepTest + /** Optionally, an extension method reference `site.name` that is + * applicable to `argType`. + */ + def extensionMethod(site: TermRef, name: TermName, argType: Type): Option[TermRef] = + site.member(name) + .alternatives + .map(mbr => TermRef(site, mbr.symbol)) + .filter(ref => + ref.symbol.is(Extension) + && isApplicableMethodRef(ref, argType :: Nil, WildcardType)) + .headOption + try - suggestionRoots + val roots = suggestionRoots .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) // don't suggest things that are imported by default + + def extensionImports = pt match + case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) => + roots.flatMap(extensionMethod(_, name, argType)) + case _ => + Nil + + roots .flatMap(_.implicitMembers.filter(shallowTest)) // filter whether the head of the implicit can match .partition(deepTest) // partition into full matches and head matches + match + case (Nil, partials) => (extensionImports, partials) + case givenImports => givenImports catch case ex: Throwable => if ctx.settings.Ydebug.value then @@ -862,7 +891,7 @@ trait Implicits { self: Typer => * The addendum suggests given imports that might fix the problem. * If there's nothing to suggest, an empty string is returned. */ - override def implicitSuggestionAddendum(pt: Type)(given ctx: Context): String = + override def importSuggestionAddendum(pt: Type)(given ctx: Context): String = val (fullMatches, headMatches) = importSuggestions(pt)(given ctx.fresh.setExploreTyperState()) implicits.println(i"suggestions for $pt in ${ctx.owner} = ($fullMatches%, %, $headMatches%, %)") @@ -873,12 +902,14 @@ trait Implicits { self: Typer => s" import ${ctx.printer.toTextRef(ref).show}" val suggestions = suggestedRefs .zip(suggestedRefs.map(importString)) - .filter(_._2.contains('.')) - .sortWith { (rs1, rs2) => // sort by specificity first, alphabetically second - val diff = compare(rs1._1, rs2._1) - diff > 0 || diff == 0 && rs1._2 < rs2._2 + .filter((ref, str) => str.contains('.')) + .sortWith { (x, y) => + // sort by specificity first, alphabetically second + val ((ref1, str1), (ref2, str2)) = (x, y) + val diff = compare(ref1, ref2) + diff > 0 || diff == 0 && str1 < str2 } - .map(_._2) + .map((ref, str) => str) .distinct // TermRefs might be different but generate the same strings if suggestions.isEmpty then "" else @@ -891,7 +922,7 @@ trait Implicits { self: Typer => | |$suggestions%\n% """ - end implicitSuggestionAddendum + end importSuggestionAddendum /** Handlers to synthesize implicits for special types */ type SpecialHandler = (Type, Span) => Context => Tree @@ -1452,7 +1483,7 @@ trait Implicits { self: Typer => // example where searching for a nested type causes an infinite loop. "" - def suggestedImports = implicitSuggestionAddendum(pt) + def suggestedImports = importSuggestionAddendum(pt) if normalImports.isEmpty then suggestedImports else normalImports end hiddenImplicitsAddendum diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index e604f8f1b1c0..3fafcef1bace 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -283,7 +283,7 @@ trait TypeAssigner { |If you do not want that, insert a `;` or empty line in front |or drop any spaces behind the operator.""" else - var add = implicitSuggestionAddendum( + var add = importSuggestionAddendum( ViewProto(qualType.widen, SelectionProto(name, WildcardType, NoViewsAllowed, privateOK = false))) if add.isEmpty then "" @@ -293,7 +293,7 @@ trait TypeAssigner { } } - def implicitSuggestionAddendum(pt: Type)(given Context): String = "" + def importSuggestionAddendum(pt: Type)(given Context): String = "" /** The type of the selection in `tree`, where `qual1` is the typed qualifier part. * The selection type is additionally checked for accessibility. diff --git a/tests/neg/missing-implicit1.check b/tests/neg/missing-implicit1.check index d2b90329f539..0dcc19f1e7cc 100644 --- a/tests/neg/missing-implicit1.check +++ b/tests/neg/missing-implicit1.check @@ -1,5 +1,5 @@ --- Error: tests/neg/missing-implicit1.scala:14:4 ----------------------------------------------------------------------- -14 | ff // error +-- Error: tests/neg/missing-implicit1.scala:17:4 ----------------------------------------------------------------------- +17 | ff // error | ^ |no implicit argument of type testObjectInstance.Zip[Option] was found for parameter xs of method ff in object testObjectInstance | @@ -7,8 +7,8 @@ | | import testObjectInstance.instances.zipOption | --- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:16:16 ---------------------------------------------- -16 | List(1, 2, 3).traverse(x => Option(x)) // error +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:19:16 ---------------------------------------------- +19 | List(1, 2, 3).traverse(x => Option(x)) // error | ^^^^^^^^^^^^^^^^^^^^^^ | value traverse is not a member of List[Int], but could be made available as an extension method. | @@ -16,8 +16,8 @@ | | import testObjectInstance.instances.traverseList | --- Error: tests/neg/missing-implicit1.scala:20:42 ---------------------------------------------------------------------- -20 | List(1, 2, 3).traverse(x => Option(x)) // error +-- Error: tests/neg/missing-implicit1.scala:23:42 ---------------------------------------------------------------------- +23 | List(1, 2, 3).traverse(x => Option(x)) // error | ^ |no implicit argument of type testObjectInstance.Zip[Option] was found for parameter evidence$1 of method traverse in trait Traverse | @@ -25,8 +25,30 @@ | | import testObjectInstance.instances.zipOption | --- Error: tests/neg/missing-implicit1.scala:36:4 ----------------------------------------------------------------------- -36 | ff // error +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:26:16 ---------------------------------------------- +26 | List(1, 2, 3).first // error + | ^^^^^^^^^^^^^^^^^^^ + | value first is not a member of List[Int], but could be made available as an extension method. + | + | The following import might fix the problem: + | + | import testObjectInstance.instances.first + | +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:27:16 ---------------------------------------------- +27 | List(1, 2, 3).second // error + | ^^^^^^^^^^^^^^^^^^^^ + | value second is not a member of List[Int], but could be made available as an extension method. + | + | The following import might fix the problem: + | + | import testObjectInstance.instances.listExtension + | +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:28:17 ---------------------------------------------- +28 | Array(1, 2, 3).first // error, no hint + | ^^^^^^^^^^^^^^^^^^^^ + | value first is not a member of Array[Int] +-- Error: tests/neg/missing-implicit1.scala:44:4 ----------------------------------------------------------------------- +44 | ff // error | ^ | no implicit argument of type Zip[Option] was found for parameter xs of method ff | @@ -34,8 +56,8 @@ | | import instances.zipOption | --- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:38:16 ---------------------------------------------- -38 | List(1, 2, 3).traverse(x => Option(x)) // error +-- [E008] Member Not Found Error: tests/neg/missing-implicit1.scala:46:16 ---------------------------------------------- +46 | List(1, 2, 3).traverse(x => Option(x)) // error | ^^^^^^^^^^^^^^^^^^^^^^ | value traverse is not a member of List[Int], but could be made available as an extension method. | @@ -43,8 +65,8 @@ | | import instances.traverseList | --- Error: tests/neg/missing-implicit1.scala:42:42 ---------------------------------------------------------------------- -42 | List(1, 2, 3).traverse(x => Option(x)) // error +-- Error: tests/neg/missing-implicit1.scala:50:42 ---------------------------------------------------------------------- +50 | List(1, 2, 3).traverse(x => Option(x)) // error | ^ |no implicit argument of type Zip[Option] was found for parameter evidence$2 of method traverse in trait Traverse | diff --git a/tests/neg/missing-implicit1.scala b/tests/neg/missing-implicit1.scala index 87f4c5f6b091..88d5bb23a51d 100644 --- a/tests/neg/missing-implicit1.scala +++ b/tests/neg/missing-implicit1.scala @@ -7,6 +7,9 @@ object testObjectInstance with object instances { given zipOption: Zip[Option] = ??? given traverseList: Traverse[List] = ??? + given listExtension: [T](xs: List[T]) extended with + def second: T = xs.tail.head + def [T](xs: List[T]) first: T = xs.head } def ff(given xs: Zip[Option]) = ??? @@ -20,6 +23,11 @@ object testObjectInstance with List(1, 2, 3).traverse(x => Option(x)) // error } + List(1, 2, 3).first // error + List(1, 2, 3).second // error + Array(1, 2, 3).first // error, no hint +end testObjectInstance + def testLocalInstance = trait Zip[F[_]] trait Traverse[F[_]] { @@ -41,3 +49,4 @@ def testLocalInstance = import instances.traverseList List(1, 2, 3).traverse(x => Option(x)) // error } +end testLocalInstance \ No newline at end of file From 427da5107dbd8f4c0c9bbf8f8cc4364a8f3604ea Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 13:44:56 +0100 Subject: [PATCH 24/27] Factor out suggestions logic into its own trait --- .../dotty/tools/dotc/typer/Implicits.scala | 239 +--------------- .../tools/dotc/typer/ImportSuggestions.scala | 258 ++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 1 + 3 files changed, 260 insertions(+), 238 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f82bbf866ace..8433335c9716 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -14,7 +14,7 @@ import Flags._ import TypeErasure.{erasure, hasStableErasure} import Mode.ImplicitsEnabled import NameOps._ -import NameKinds.{LazyImplicitName, FlatName} +import NameKinds.LazyImplicitName import Symbols._ import Denotations._ import Types._ @@ -37,8 +37,6 @@ import config.Printers.{implicits, implicitsDetailed} import collection.mutable import reporting.trace import annotation.tailrec -import scala.util.control.NonFatal -import java.util.{Timer, TimerTask} import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -69,12 +67,6 @@ object Implicits { final val Extension = 4 } - /** Timeout to test a single implicit value as a suggestion, in ms */ - val testOneImplicitTimeOut = 500 - - /** Global timeout to stop looking for further implicit suggestions, in ms */ - val suggestImplicitTimeOut = 10000 - /** If `expected` is a selection prototype, does `tp` have an extension * method with the selecting name? False otherwise. */ @@ -695,235 +687,6 @@ trait Implicits { self: Typer => } } - /** A list of TermRefs referring to the roots where suggestions for - * imports of givens or extension methods that might fix a type error - * are searched. - * - * These roots are the smallest set of objects and packages that includes - * - * - any object that is a defined in an enclosing scope, - * - any object that is a member of an enclosing class, - * - any enclosing package (including the root package), - * - any object that is a member of a searched object or package, - * - any object or package from which something is imported in an enclosing scope, - * - any package that is nested in a searched package, provided - * the package was accessed in some way previously. - * - * Excluded from the root set are: - * - * - Objects that contain `$`s in their name. These have to - * be omitted since they might be inner Java class files which - * cannot be read by the ClassfileParser without crashing. - * - Any members of static parts of Java classes. - * - Any members of the empty package. These should be - * skipped since the empty package often contains unrelated junk files - * that should not be used for suggestions. - * - Any members of the java or java.lang packages. These are - * skipped as an optimization, since they won't contain implicits anyway. - */ - private def suggestionRoots(given Context) = - val seen = mutable.Set[TermRef]() - - def lookInside(root: Symbol)(given Context): Boolean = - if root.is(Package) then root.isTerm && root.isCompleted - else !root.name.is(FlatName) - && !root.name.lastPart.contains('$') - && root.is(ModuleVal, butNot = JavaDefined) - - def nestedRoots(site: Type)(given Context): List[Symbol] = - val seenNames = mutable.Set[Name]() - site.baseClasses.flatMap { bc => - bc.info.decls.filter { dcl => - lookInside(dcl) - && !seenNames.contains(dcl.name) - && { seenNames += dcl.name; true } - } - } - - def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] = - val site = ref.widen - val refSym = site.typeSymbol - val nested = - if refSym.is(Package) then - if refSym == defn.EmptyPackageClass // Don't search the empty package - || refSym == defn.JavaPackageClass // As an optimization, don't search java... - || refSym == defn.JavaLangPackageClass // ... or java.lang. - then Nil - else refSym.info.decls.filter(lookInside) - else - if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion - if refSym.is(JavaDefined) then Nil - else nestedRoots(site) - nested - .map(mbr => TermRef(ref, mbr.asTerm)) - .flatMap(rootsIn) - .toList - - def rootsIn(ref: TermRef)(given Context): List[TermRef] = - if seen.contains(ref) then Nil - else - implicits.println(i"search for suggestions in ${ref.symbol.fullName}") - seen += ref - ref :: rootsStrictlyIn(ref) - - def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match - case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) - case _ => Nil - - def recur(given ctx: Context): List[TermRef] = - if ctx.owner.exists then - val defined = - if ctx.owner.isClass then - if ctx.owner eq ctx.outer.owner then Nil - else rootsStrictlyIn(ctx.owner.thisType) - else - if ctx.scope eq ctx.outer.scope then Nil - else ctx.scope - .filter(lookInside(_)) - .flatMap(sym => rootsIn(sym.termRef)) - val imported = - if ctx.importInfo eq ctx.outer.importInfo then Nil - else ctx.importInfo.sym.info match - case ImportType(expr) => rootsOnPath(expr.tpe) - case _ => Nil - defined ++ imported ++ recur(given ctx.outer) - else Nil - - recur - end suggestionRoots - - /** Given an expected type `pt`, return two lists of TermRefs: - * - * 1. The _fully matching_ given instances that can be completed - * to a full synthesized given term that matches the expected type `pt`. - * - * 2. The _head matching_ given instances, that conform to the - * expected type `pt`, ignoring any dependent implicit arguments. - * - * If there are no fully matching given instances under (1), and `pt` is - * a view prototype of a selection of the form `T ?=>? { name: ... }`, - * return instead a list of all possible references to extension methods named - * `name` that are applicable to `T`. - */ - private def importSuggestions(pt: Type)(given ctx: Context): (List[TermRef], List[TermRef]) = - val timer = new Timer() - val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut - - /** Test whether the head of a given instance matches the expected type `pt`, - * ignoring any dependent implicit arguments. - */ - def shallowTest(ref: TermRef): Boolean = - System.currentTimeMillis < deadLine - && (ref <:< pt)(given ctx.fresh.setExploreTyperState()) - - /** Test whether a full given term can be synthesized that matches - * the expected type `pt`. - */ - def deepTest(ref: TermRef): Boolean = - System.currentTimeMillis < deadLine - && { - val task = new TimerTask with - def run() = - println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") - ctx.run.isCancelled = true - val span = ctx.owner.sourcePos.span - val (expectedType, argument, kind) = pt match - case ViewProto(argType, resType) => - (resType, - untpd.Ident(ref.name).withSpan(span).withType(argType), - if hasExtMethod(ref, resType) then Candidate.Extension - else Candidate.Conversion) - case _ => - (pt, EmptyTree, Candidate.Value) - val candidate = Candidate(ref, kind, 0) - try - timer.schedule(task, testOneImplicitTimeOut) - typedImplicit(candidate, expectedType, argument, span)( - given ctx.fresh.setExploreTyperState()).isSuccess - finally - task.cancel() - ctx.run.isCancelled = false - } - end deepTest - - /** Optionally, an extension method reference `site.name` that is - * applicable to `argType`. - */ - def extensionMethod(site: TermRef, name: TermName, argType: Type): Option[TermRef] = - site.member(name) - .alternatives - .map(mbr => TermRef(site, mbr.symbol)) - .filter(ref => - ref.symbol.is(Extension) - && isApplicableMethodRef(ref, argType :: Nil, WildcardType)) - .headOption - - try - val roots = suggestionRoots - .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) - // don't suggest things that are imported by default - - def extensionImports = pt match - case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) => - roots.flatMap(extensionMethod(_, name, argType)) - case _ => - Nil - - roots - .flatMap(_.implicitMembers.filter(shallowTest)) - // filter whether the head of the implicit can match - .partition(deepTest) - // partition into full matches and head matches - match - case (Nil, partials) => (extensionImports, partials) - case givenImports => givenImports - catch - case ex: Throwable => - if ctx.settings.Ydebug.value then - println("caught exception when searching for suggestions") - ex.printStackTrace() - (Nil, Nil) - finally timer.cancel() - end importSuggestions - - /** An addendum to an error message where the error might be fixed - * by some implicit value of type `pt` that is however not found. - * The addendum suggests given imports that might fix the problem. - * If there's nothing to suggest, an empty string is returned. - */ - override def importSuggestionAddendum(pt: Type)(given ctx: Context): String = - val (fullMatches, headMatches) = - importSuggestions(pt)(given ctx.fresh.setExploreTyperState()) - implicits.println(i"suggestions for $pt in ${ctx.owner} = ($fullMatches%, %, $headMatches%, %)") - val (suggestedRefs, help) = - if fullMatches.nonEmpty then (fullMatches, "fix") - else (headMatches, "make progress towards fixing") - def importString(ref: TermRef): String = - s" import ${ctx.printer.toTextRef(ref).show}" - val suggestions = suggestedRefs - .zip(suggestedRefs.map(importString)) - .filter((ref, str) => str.contains('.')) - .sortWith { (x, y) => - // sort by specificity first, alphabetically second - val ((ref1, str1), (ref2, str2)) = (x, y) - val diff = compare(ref1, ref2) - diff > 0 || diff == 0 && str1 < str2 - } - .map((ref, str) => str) - .distinct // TermRefs might be different but generate the same strings - if suggestions.isEmpty then "" - else - val fix = - if suggestions.tail.isEmpty then "The following import" - else "One of the following imports" - i""" - | - |$fix might $help the problem: - | - |$suggestions%\n% - """ - end importSuggestionAddendum - /** Handlers to synthesize implicits for special types */ type SpecialHandler = (Type, Span) => Context => Tree type SpecialHandlers = List[(ClassSymbol, SpecialHandler)] diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala new file mode 100644 index 000000000000..3a0bbce1aa12 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -0,0 +1,258 @@ +package dotty.tools +package dotc +package typer + +import core._ +import Contexts._, Types._, Symbols._, Names._, Decorators._, ProtoTypes._ +import Flags._ +import NameKinds.FlatName +import config.Printers.implicits +import util.Spans.Span +import ast.{untpd, tpd} +import Implicits.{hasExtMethod, Candidate} +import java.util.{Timer, TimerTask} +import collection.mutable + +/** This trait defines the method `importSuggestionAddendum` that adds an addendum + * to error messages suggesting additional imports. + */ +trait ImportSuggestions with + this: Typer => + + import tpd._ + + /** Timeout to test a single implicit value as a suggestion, in ms */ + private inline val testOneImplicitTimeOut = 500 + + /** Global timeout to stop looking for further implicit suggestions, in ms */ + private inline val suggestImplicitTimeOut = 10000 + + /** A list of TermRefs referring to the roots where suggestions for + * imports of givens or extension methods that might fix a type error + * are searched. + * + * These roots are the smallest set of objects and packages that includes + * + * - any object that is a defined in an enclosing scope, + * - any object that is a member of an enclosing class, + * - any enclosing package (including the root package), + * - any object that is a member of a searched object or package, + * - any object or package from which something is imported in an enclosing scope, + * - any package that is nested in a searched package, provided + * the package was accessed in some way previously. + * + * Excluded from the root set are: + * + * - Objects that contain `$`s in their name. These have to + * be omitted since they might be inner Java class files which + * cannot be read by the ClassfileParser without crashing. + * - Any members of static parts of Java classes. + * - Any members of the empty package. These should be + * skipped since the empty package often contains unrelated junk files + * that should not be used for suggestions. + * - Any members of the java or java.lang packages. These are + * skipped as an optimization, since they won't contain implicits anyway. + */ + private def suggestionRoots(given Context) = + val seen = mutable.Set[TermRef]() + + def lookInside(root: Symbol)(given Context): Boolean = + if root.is(Package) then root.isTerm && root.isCompleted + else !root.name.is(FlatName) + && !root.name.lastPart.contains('$') + && root.is(ModuleVal, butNot = JavaDefined) + + def nestedRoots(site: Type)(given Context): List[Symbol] = + val seenNames = mutable.Set[Name]() + site.baseClasses.flatMap { bc => + bc.info.decls.filter { dcl => + lookInside(dcl) + && !seenNames.contains(dcl.name) + && { seenNames += dcl.name; true } + } + } + + def rootsStrictlyIn(ref: Type)(given Context): List[TermRef] = + val site = ref.widen + val refSym = site.typeSymbol + val nested = + if refSym.is(Package) then + if refSym == defn.EmptyPackageClass // Don't search the empty package + || refSym == defn.JavaPackageClass // As an optimization, don't search java... + || refSym == defn.JavaLangPackageClass // ... or java.lang. + then Nil + else refSym.info.decls.filter(lookInside) + else + if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion + if refSym.is(JavaDefined) then Nil + else nestedRoots(site) + nested + .map(mbr => TermRef(ref, mbr.asTerm)) + .flatMap(rootsIn) + .toList + + def rootsIn(ref: TermRef)(given Context): List[TermRef] = + if seen.contains(ref) then Nil + else + implicits.println(i"search for suggestions in ${ref.symbol.fullName}") + seen += ref + ref :: rootsStrictlyIn(ref) + + def rootsOnPath(tp: Type)(given Context): List[TermRef] = tp match + case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) + case _ => Nil + + def recur(given ctx: Context): List[TermRef] = + if ctx.owner.exists then + val defined = + if ctx.owner.isClass then + if ctx.owner eq ctx.outer.owner then Nil + else rootsStrictlyIn(ctx.owner.thisType) + else + if ctx.scope eq ctx.outer.scope then Nil + else ctx.scope + .filter(lookInside(_)) + .flatMap(sym => rootsIn(sym.termRef)) + val imported = + if ctx.importInfo eq ctx.outer.importInfo then Nil + else ctx.importInfo.sym.info match + case ImportType(expr) => rootsOnPath(expr.tpe) + case _ => Nil + defined ++ imported ++ recur(given ctx.outer) + else Nil + + recur + end suggestionRoots + + /** Given an expected type `pt`, return two lists of TermRefs: + * + * 1. The _fully matching_ given instances that can be completed + * to a full synthesized given term that matches the expected type `pt`. + * + * 2. The _head matching_ given instances, that conform to the + * expected type `pt`, ignoring any dependent implicit arguments. + * + * If there are no fully matching given instances under (1), and `pt` is + * a view prototype of a selection of the form `T ?=>? { name: ... }`, + * return instead a list of all possible references to extension methods named + * `name` that are applicable to `T`. + */ + private def importSuggestions(pt: Type)(given ctx: Context): (List[TermRef], List[TermRef]) = + val timer = new Timer() + val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut + + /** Test whether the head of a given instance matches the expected type `pt`, + * ignoring any dependent implicit arguments. + */ + def shallowTest(ref: TermRef): Boolean = + System.currentTimeMillis < deadLine + && (ref <:< pt)(given ctx.fresh.setExploreTyperState()) + + /** Test whether a full given term can be synthesized that matches + * the expected type `pt`. + */ + def deepTest(ref: TermRef): Boolean = + System.currentTimeMillis < deadLine + && { + val task = new TimerTask with + def run() = + println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + ctx.run.isCancelled = true + val span = ctx.owner.sourcePos.span + val (expectedType, argument, kind) = pt match + case ViewProto(argType, resType) => + (resType, + untpd.Ident(ref.name).withSpan(span).withType(argType), + if hasExtMethod(ref, resType) then Candidate.Extension + else Candidate.Conversion) + case _ => + (pt, EmptyTree, Candidate.Value) + val candidate = Candidate(ref, kind, 0) + try + timer.schedule(task, testOneImplicitTimeOut) + typedImplicit(candidate, expectedType, argument, span)( + given ctx.fresh.setExploreTyperState()).isSuccess + finally + task.cancel() + ctx.run.isCancelled = false + } + end deepTest + + /** Optionally, an extension method reference `site.name` that is + * applicable to `argType`. + */ + def extensionMethod(site: TermRef, name: TermName, argType: Type): Option[TermRef] = + site.member(name) + .alternatives + .map(mbr => TermRef(site, mbr.symbol)) + .filter(ref => + ref.symbol.is(Extension) + && isApplicableMethodRef(ref, argType :: Nil, WildcardType)) + .headOption + + try + val roots = suggestionRoots + .filterNot(root => defn.RootImportTypes.exists(_.symbol == root.symbol)) + // don't suggest things that are imported by default + + def extensionImports = pt match + case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) => + roots.flatMap(extensionMethod(_, name, argType)) + case _ => + Nil + + roots + .flatMap(_.implicitMembers.filter(shallowTest)) + // filter whether the head of the implicit can match + .partition(deepTest) + // partition into full matches and head matches + match + case (Nil, partials) => (extensionImports, partials) + case givenImports => givenImports + catch + case ex: Throwable => + if ctx.settings.Ydebug.value then + println("caught exception when searching for suggestions") + ex.printStackTrace() + (Nil, Nil) + finally timer.cancel() + end importSuggestions + + /** An addendum to an error message where the error might be fixed + * by some implicit value of type `pt` that is however not found. + * The addendum suggests given imports that might fix the problem. + * If there's nothing to suggest, an empty string is returned. + */ + override def importSuggestionAddendum(pt: Type)(given ctx: Context): String = + val (fullMatches, headMatches) = + importSuggestions(pt)(given ctx.fresh.setExploreTyperState()) + implicits.println(i"suggestions for $pt in ${ctx.owner} = ($fullMatches%, %, $headMatches%, %)") + val (suggestedRefs, help) = + if fullMatches.nonEmpty then (fullMatches, "fix") + else (headMatches, "make progress towards fixing") + def importString(ref: TermRef): String = + s" import ${ctx.printer.toTextRef(ref).show}" + val suggestions = suggestedRefs + .zip(suggestedRefs.map(importString)) + .filter((ref, str) => str.contains('.')) + .sortWith { (x, y) => + // sort by specificity first, alphabetically second + val ((ref1, str1), (ref2, str2)) = (x, y) + val diff = compare(ref1, ref2) + diff > 0 || diff == 0 && str1 < str2 + } + .map((ref, str) => str) + .distinct // TermRefs might be different but generate the same strings + if suggestions.isEmpty then "" + else + val fix = + if suggestions.tail.isEmpty then "The following import" + else "One of the following imports" + i""" + | + |$fix might $help the problem: + | + |$suggestions%\n% + """ + end importSuggestionAddendum +end ImportSuggestions diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4179aeb92c6f..248706dc4db0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -84,6 +84,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits + with ImportSuggestions with Inferencing with Dynamic with Checking From 5ac8b800023b90205272fef23be159a7e85484d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 14:14:22 +0100 Subject: [PATCH 25/27] Also suggest partial matches for missing implicit arguments This used to work only for ViewProtos before. --- .../tools/dotc/typer/ImportSuggestions.scala | 7 ++++++- tests/neg/missing-implicit2.check | 18 ++++++++++++++++++ tests/neg/missing-implicit2.scala | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/neg/missing-implicit2.check create mode 100644 tests/neg/missing-implicit2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 3a0bbce1aa12..bd8641bc49fa 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -146,7 +146,12 @@ trait ImportSuggestions with */ def shallowTest(ref: TermRef): Boolean = System.currentTimeMillis < deadLine - && (ref <:< pt)(given ctx.fresh.setExploreTyperState()) + && { + given Context = ctx.fresh.setExploreTyperState() + pt match + case pt: ViewProto => pt.isMatchedBy(ref) + case _ => normalize(ref, pt) <:< pt + } /** Test whether a full given term can be synthesized that matches * the expected type `pt`. diff --git a/tests/neg/missing-implicit2.check b/tests/neg/missing-implicit2.check new file mode 100644 index 000000000000..59106a0042e4 --- /dev/null +++ b/tests/neg/missing-implicit2.check @@ -0,0 +1,18 @@ +-- Error: tests/neg/missing-implicit2.scala:10:18 ---------------------------------------------------------------------- +10 | f(given xFromY) // error + | ^ + | no implicit argument of type Y was found for parameter y of method xFromY + | + | The following import might fix the problem: + | + | import test.instances.y + | +-- Error: tests/neg/missing-implicit2.scala:16:5 ----------------------------------------------------------------------- +16 | f // error + | ^ + | no implicit argument of type X was found for parameter x of method f in object test + | + | The following import might make progress towards fixing the problem: + | + | import instances2.xFromY + | diff --git a/tests/neg/missing-implicit2.scala b/tests/neg/missing-implicit2.scala new file mode 100644 index 000000000000..e9ad60a2d368 --- /dev/null +++ b/tests/neg/missing-implicit2.scala @@ -0,0 +1,17 @@ +trait X +trait Y +object test with + def f(given x: X) = ??? + object instances { + given y: Y = ??? + } + locally { + given xFromY(given y: Y): X = ??? + f(given xFromY) // error + } + locally { + object instances2 { + given xFromY: Y => X = ??? + } + f // error + } From 8289d51c3955b9acdbd65b7f9028b6be4e8f118e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 14:49:20 +0100 Subject: [PATCH 26/27] Don't mention evidence$n parameters in error message --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 5 +++-- tests/neg/missing-implicit1.check | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 8433335c9716..58456da5dcaf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -14,7 +14,7 @@ import Flags._ import TypeErasure.{erasure, hasStableErasure} import Mode.ImplicitsEnabled import NameOps._ -import NameKinds.LazyImplicitName +import NameKinds.{LazyImplicitName, EvidenceParamName} import Symbols._ import Denotations._ import Types._ @@ -1265,7 +1265,8 @@ trait Implicits { self: Typer => def addendum = if (qt1 eq qt) "" else (i"\nwhich is an alias of: $qt1") em"parameter of ${qual.tpe.widen}$addendum" case _ => - em"parameter ${paramName} of $methodStr" + em"${ if paramName.is(EvidenceParamName) then "an implicit parameter" + else s"parameter $paramName" } of $methodStr" } private def strictEquality(implicit ctx: Context): Boolean = diff --git a/tests/neg/missing-implicit1.check b/tests/neg/missing-implicit1.check index 0dcc19f1e7cc..f04a2c248b69 100644 --- a/tests/neg/missing-implicit1.check +++ b/tests/neg/missing-implicit1.check @@ -19,7 +19,7 @@ -- Error: tests/neg/missing-implicit1.scala:23:42 ---------------------------------------------------------------------- 23 | List(1, 2, 3).traverse(x => Option(x)) // error | ^ - |no implicit argument of type testObjectInstance.Zip[Option] was found for parameter evidence$1 of method traverse in trait Traverse + |no implicit argument of type testObjectInstance.Zip[Option] was found for an implicit parameter of method traverse in trait Traverse | |The following import might fix the problem: | @@ -68,7 +68,7 @@ -- Error: tests/neg/missing-implicit1.scala:50:42 ---------------------------------------------------------------------- 50 | List(1, 2, 3).traverse(x => Option(x)) // error | ^ - |no implicit argument of type Zip[Option] was found for parameter evidence$2 of method traverse in trait Traverse + |no implicit argument of type Zip[Option] was found for an implicit parameter of method traverse in trait Traverse | |The following import might fix the problem: | From 6f94a835c513da53a2e8d1e3bc3e72d854c9c927 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Jan 2020 16:14:49 +0100 Subject: [PATCH 27/27] Fix unrelated test --- tests/pos/i7700.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/pos/i7700.scala b/tests/pos/i7700.scala index 913285822c31..bdca41ff87ce 100644 --- a/tests/pos/i7700.scala +++ b/tests/pos/i7700.scala @@ -1,12 +1,12 @@ package test -trait Show[-A] +trait Show[-A] with def show(a: A): String -object Macros +object Macros with inline def (sc: StringContext) show(args: =>Any*): String = ??? -object Show +object Show with def[A] (a: A) show(given S: Show[A]): String = S.show(a) export Macros.show \ No newline at end of file