Skip to content

Commit 9ceed92

Browse files
authored
Merge pull request #1658 from dotty-staging/fix-#1639
Fix #1639: Changes around implicits and apply methods
2 parents 8288a34 + d8f5c6c commit 9ceed92

File tree

6 files changed

+111
-56
lines changed

6 files changed

+111
-56
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,16 @@ abstract class Reporter extends interfaces.ReporterResult {
286286
}
287287

288288
/** Should this diagnostic not be reported at all? */
289-
def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = ctx.mode.is(Mode.Printing)
289+
def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean =
290+
ctx.mode.is(Mode.Printing)
290291

291292
/** Does this reporter contain not yet reported errors or warnings? */
292293
def hasPending: Boolean = false
293294

295+
/** If this reporter buffers messages, remove and return all buffered messages. */
296+
def removeBufferedMessages(implicit ctx: Context): List[MessageContainer] = Nil
297+
294298
/** Issue all error messages in this reporter to next outer one, or make sure they are written. */
295-
def flush()(implicit ctx: Context): Unit = {}
299+
def flush()(implicit ctx: Context): Unit =
300+
removeBufferedMessages.foreach(ctx.reporter.report)
296301
}

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,9 @@ class StoreReporter(outer: Reporter) extends Reporter {
3636
}
3737
}
3838

39-
override def flush()(implicit ctx: Context) =
40-
if (infos != null) {
41-
infos.foreach(ctx.reporter.report(_))
42-
infos = null
43-
}
39+
override def removeBufferedMessages(implicit ctx: Context): List[MessageContainer] =
40+
if (infos != null) try infos.toList finally infos = null
41+
else Nil
4442

4543
override def errorsReported = hasErrors || outer.errorsReported
4644
}

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

Lines changed: 66 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Constants._
2424
import Applications._
2525
import ProtoTypes._
2626
import ErrorReporting._
27+
import reporting.diagnostic.MessageContainer
2728
import Inferencing.fullyDefinedType
2829
import Trees._
2930
import Hashable._
@@ -212,6 +213,8 @@ object Implicits {
212213
/** A "no matching implicit found" failure */
213214
case object NoImplicitMatches extends SearchFailure
214215

216+
case object DivergingImplicit extends SearchFailure
217+
215218
/** A search failure that can show information about the cause */
216219
abstract class ExplainedSearchFailure extends SearchFailure {
217220
protected def pt: Type
@@ -233,9 +236,35 @@ object Implicits {
233236
"\n " + explanation
234237
}
235238

236-
class NonMatchingImplicit(ref: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure {
237-
def explanation(implicit ctx: Context): String =
238-
em"${err.refStr(ref)} does not $qualify"
239+
class NonMatchingImplicit(ref: TermRef,
240+
val pt: Type,
241+
val argument: tpd.Tree,
242+
trail: List[MessageContainer]) extends ExplainedSearchFailure {
243+
private val separator = "\n**** because ****\n"
244+
245+
/** Replace repeated parts beginning with `separator` by ... */
246+
private def elideRepeated(str: String): String = {
247+
val startIdx = str.indexOfSlice(separator)
248+
val nextIdx = str.indexOfSlice(separator, startIdx + separator.length)
249+
if (nextIdx < 0) str
250+
else {
251+
val prefix = str.take(startIdx)
252+
val first = str.slice(startIdx, nextIdx)
253+
var rest = str.drop(nextIdx)
254+
if (rest.startsWith(first)) {
255+
rest = rest.drop(first.length)
256+
val dots = "\n\n ...\n"
257+
if (!rest.startsWith(dots)) rest = dots ++ rest
258+
}
259+
prefix ++ first ++ rest
260+
}
261+
}
262+
263+
def explanation(implicit ctx: Context): String = {
264+
val headMsg = em"${err.refStr(ref)} does not $qualify"
265+
val trailMsg = trail.map(mc => i"$separator ${mc.message}").mkString
266+
elideRepeated(headMsg ++ trailMsg)
267+
}
239268
}
240269

241270
class ShadowedImplicit(ref: TermRef, shadowing: Type, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure {
@@ -273,9 +302,10 @@ trait ImplicitRunInfo { self: RunInfo =>
273302
* a type variable, we need the current context, the current
274303
* runinfo context does not do.
275304
*/
276-
def implicitScope(tp: Type, liftingCtx: Context): OfTypeImplicits = {
305+
def implicitScope(rootTp: Type, liftingCtx: Context): OfTypeImplicits = {
277306

278307
val seen: mutable.Set[Type] = mutable.Set()
308+
val incomplete: mutable.Set[Type] = mutable.Set()
279309

280310
/** Replace every typeref that does not refer to a class by a conjunction of class types
281311
* that has the same implicit scope as the original typeref. The motivation for applying
@@ -309,16 +339,23 @@ trait ImplicitRunInfo { self: RunInfo =>
309339
}
310340
}
311341

312-
def iscopeRefs(tp: Type): TermRefSet =
313-
if (seen contains tp) EmptyTermRefSet
314-
else {
315-
seen += tp
316-
iscope(tp).companionRefs
317-
}
318-
319342
// todo: compute implicits directly, without going via companionRefs?
320343
def collectCompanions(tp: Type): TermRefSet = track("computeImplicitScope") {
321344
ctx.traceIndented(i"collectCompanions($tp)", implicits) {
345+
346+
def iscopeRefs(t: Type): TermRefSet = implicitScopeCache.get(t) match {
347+
case Some(is) =>
348+
is.companionRefs
349+
case None =>
350+
if (seen contains t) {
351+
incomplete += tp // all references to rootTo will be accounted for in `seen` so we return `EmptySet`.
352+
EmptyTermRefSet // on the other hand, the refs of `tp` are now not accurate, so `tp` is marked incomplete.
353+
} else {
354+
seen += t
355+
iscope(t).companionRefs
356+
}
357+
}
358+
322359
val comps = new TermRefSet
323360
tp match {
324361
case tp: NamedType =>
@@ -356,7 +393,8 @@ trait ImplicitRunInfo { self: RunInfo =>
356393
* @param isLifted Type `tp` is the result of a `liftToClasses` application
357394
*/
358395
def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = {
359-
def computeIScope(cacheResult: Boolean) = {
396+
val canCache = Config.cacheImplicitScopes && tp.hash != NotCached
397+
def computeIScope() = {
360398
val savedEphemeral = ctx.typerState.ephemeral
361399
ctx.typerState.ephemeral = false
362400
try {
@@ -367,33 +405,23 @@ trait ImplicitRunInfo { self: RunInfo =>
367405
else
368406
collectCompanions(tp)
369407
val result = new OfTypeImplicits(tp, refs)(ctx)
370-
if (ctx.typerState.ephemeral) record("ephemeral cache miss: implicitScope")
371-
else if (cacheResult) implicitScopeCache(tp) = result
408+
if (ctx.typerState.ephemeral)
409+
record("ephemeral cache miss: implicitScope")
410+
else if (canCache &&
411+
((tp eq rootTp) || // first type traversed is always cached
412+
!incomplete.contains(tp) && // other types are cached if they are not incomplete
413+
result.companionRefs.forall( // and all their companion refs are cached
414+
implicitScopeCache.contains)))
415+
implicitScopeCache(tp) = result
372416
result
373417
}
374418
finally ctx.typerState.ephemeral |= savedEphemeral
375419
}
376-
377-
if (tp.hash == NotCached || !Config.cacheImplicitScopes)
378-
computeIScope(cacheResult = false)
379-
else implicitScopeCache get tp match {
380-
case Some(is) => is
381-
case None =>
382-
// Implicit scopes are tricky to cache because of loops. For example
383-
// in `tests/pos/implicit-scope-loop.scala`, the scope of B contains
384-
// the scope of A which contains the scope of B. We break the loop
385-
// by returning EmptyTermRefSet in `collectCompanions` for types
386-
// that we have already seen, but this means that we cannot cache
387-
// the computed scope of A, it is incomplete.
388-
// Keeping track of exactly where these loops happen would require a
389-
// lot of book-keeping, instead we choose to be conservative and only
390-
// cache scopes before any type has been seen. This is unfortunate
391-
// because loops are very common for types in scala.collection.
392-
computeIScope(cacheResult = seen.isEmpty)
393-
}
420+
if (canCache) implicitScopeCache.getOrElse(tp, computeIScope())
421+
else computeIScope()
394422
}
395423

396-
iscope(tp)
424+
iscope(rootTp)
397425
}
398426

399427
/** A map that counts the number of times an implicit ref was picked */
@@ -587,7 +615,7 @@ trait Implicits { self: Typer =>
587615
val wildProto = implicitProto(pt, wildApprox(_))
588616

589617
/** Search failures; overridden in ExplainedImplicitSearch */
590-
protected def nonMatchingImplicit(ref: TermRef): SearchFailure = NoImplicitMatches
618+
protected def nonMatchingImplicit(ref: TermRef, trail: List[MessageContainer]): SearchFailure = NoImplicitMatches
591619
protected def divergingImplicit(ref: TermRef): SearchFailure = NoImplicitMatches
592620
protected def shadowedImplicit(ref: TermRef, shadowing: Type): SearchFailure = NoImplicitMatches
593621
protected def failedSearch: SearchFailure = NoImplicitMatches
@@ -628,7 +656,7 @@ trait Implicits { self: Typer =>
628656
{ implicits.println(i"invalid eqAny[$tp1, $tp2]"); false }
629657
}
630658
if (ctx.reporter.hasErrors)
631-
nonMatchingImplicit(ref)
659+
nonMatchingImplicit(ref, ctx.reporter.removeBufferedMessages)
632660
else if (contextual && !ctx.mode.is(Mode.ImplicitShadowing) &&
633661
!shadowing.tpe.isError && !refMatches(shadowing)) {
634662
implicits.println(i"SHADOWING $ref in ${ref.termSymbol.owner} is shadowed by $shadowing in ${shadowing.symbol.owner}")
@@ -637,7 +665,7 @@ trait Implicits { self: Typer =>
637665
else generated1 match {
638666
case TypeApply(fn, targs @ (arg1 :: arg2 :: Nil))
639667
if fn.symbol == defn.Predef_eqAny && !validEqAnyArgs(arg1.tpe, arg2.tpe) =>
640-
nonMatchingImplicit(ref)
668+
nonMatchingImplicit(ref, Nil)
641669
case _ =>
642670
SearchSuccess(generated1, ref, ctx.typerState)
643671
}
@@ -743,8 +771,8 @@ trait Implicits { self: Typer =>
743771
fail
744772
}
745773
def failures = myFailures.toList
746-
override def nonMatchingImplicit(ref: TermRef) =
747-
record(new NonMatchingImplicit(ref, pt, argument))
774+
override def nonMatchingImplicit(ref: TermRef, trail: List[MessageContainer]) =
775+
record(new NonMatchingImplicit(ref, pt, argument, trail))
748776
override def divergingImplicit(ref: TermRef) =
749777
record(new DivergingImplicit(ref, pt, argument))
750778
override def shadowedImplicit(ref: TermRef, shadowing: Type): SearchFailure =

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ class ReTyper extends Typer {
7474
override def index(trees: List[untpd.Tree])(implicit ctx: Context) = ctx
7575
override def annotate(trees: List[untpd.Tree])(implicit ctx: Context) = ()
7676

77-
override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: (Tree, TyperState) => Tree)(implicit ctx: Context): Tree =
78-
fallBack(tree, ctx.typerState)
77+
override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: => Tree)(implicit ctx: Context): Tree =
78+
fallBack
7979

8080
override def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = ()
8181

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,18 +1613,34 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
16131613
* `fallBack`.
16141614
*
16151615
* 1st strategy: Try to insert `.apply` so that the result conforms to prototype `pt`.
1616+
* This strategy is not tried if the prototype represents already
1617+
* another `.apply` or `.apply()` selection.
16161618
* 2nd strategy: If tree is a select `qual.name`, try to insert an implicit conversion
16171619
* around the qualifier part `qual` so that the result conforms to the expected type
16181620
* with wildcard result type.
16191621
*/
1620-
def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: (Tree, TyperState) => Tree)(implicit ctx: Context): Tree =
1621-
tryEither { implicit ctx =>
1622+
def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: => Tree)(implicit ctx: Context): Tree = {
1623+
1624+
/** Is `pt` a prototype of an `apply` selection, or a parameterless function yielding one? */
1625+
def isApplyProto(pt: Type): Boolean = pt match {
1626+
case pt: SelectionProto => pt.name == nme.apply
1627+
case pt: FunProto => pt.args.isEmpty && isApplyProto(pt.resultType)
1628+
case pt: IgnoredProto => isApplyProto(pt.ignored)
1629+
case _ => false
1630+
}
1631+
1632+
def tryApply(implicit ctx: Context) = {
16221633
val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt)
16231634
if (sel.tpe.isError) sel else adapt(sel, pt)
1624-
} { (failedTree, failedState) =>
1625-
tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack(failedTree, failedState))
16261635
}
16271636

1637+
def tryImplicit =
1638+
tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack)
1639+
1640+
if (isApplyProto(pt)) tryImplicit
1641+
else tryEither(tryApply(_))((_, _) => tryImplicit)
1642+
}
1643+
16281644
/** If this tree is a select node `qual.name`, try to insert an implicit conversion
16291645
* `c` around `qual` so that `c(qual).name` conforms to `pt`.
16301646
*/
@@ -1716,7 +1732,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
17161732
def hasEmptyParams(denot: SingleDenotation) = denot.info.paramTypess == ListOfNil
17171733
pt match {
17181734
case pt: FunProto =>
1719-
tryInsertApplyOrImplicit(tree, pt)((_, _) => noMatches)
1735+
tryInsertApplyOrImplicit(tree, pt)(noMatches)
17201736
case _ =>
17211737
if (altDenots exists (_.info.paramTypess == ListOfNil))
17221738
typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt)
@@ -1755,7 +1771,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
17551771
case Apply(_, _) => " more"
17561772
case _ => ""
17571773
}
1758-
(_, _) => errorTree(tree, em"$methodStr does not take$more parameters")
1774+
errorTree(tree, em"$methodStr does not take$more parameters")
17591775
}
17601776
}
17611777

@@ -1974,9 +1990,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
19741990
case pt: FunProto =>
19751991
adaptToArgs(wtp, pt)
19761992
case pt: PolyProto =>
1977-
tryInsertApplyOrImplicit(tree, pt) {
1978-
(_, _) => tree // error will be reported in typedTypeApply
1979-
}
1993+
tryInsertApplyOrImplicit(tree, pt)(tree) // error will be reported in typedTypeApply
19801994
case _ =>
19811995
if (ctx.mode is Mode.Type) adaptType(tree.tpe)
19821996
else adaptNoArgs(wtp)

tests/neg/i1639.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Bar {
2+
implicit def f(implicit x: String): String = x
3+
4+
implicitly[String](f) // error: divergent (turn -explaintypes on to see it)
5+
}
6+
7+
class Foo(implicit val bar: String) {
8+
def this() = this("baz") // error: none of the alternatives match arguments
9+
}
10+

0 commit comments

Comments
 (0)