@@ -26,8 +26,8 @@ import Scopes.newScope
26
26
import Typer .BindingPrec , BindingPrec .*
27
27
import Hashable .*
28
28
import util .{EqHashMap , Stats }
29
- import config .{Config , Feature }
30
- import Feature .migrateTo3
29
+ import config .{Config , Feature , SourceVersion }
30
+ import Feature .{ migrateTo3 , sourceVersion }
31
31
import config .Printers .{implicits , implicitsDetailed }
32
32
import collection .mutable
33
33
import reporting .*
@@ -324,7 +324,7 @@ object Implicits:
324
324
/** Is this the outermost implicits? This is the case if it either the implicits
325
325
* of NoContext, or the last one before it.
326
326
*/
327
- private def isOuterMost = {
327
+ private def isOutermost = {
328
328
val finalImplicits = NoContext .implicits
329
329
(this eq finalImplicits) || (outerImplicits eqn finalImplicits)
330
330
}
@@ -356,7 +356,7 @@ object Implicits:
356
356
Stats .record(" uncached eligible" )
357
357
if monitored then record(s " check uncached eligible refs in irefCtx " , refs.length)
358
358
val ownEligible = filterMatching(tp)
359
- if isOuterMost then ownEligible
359
+ if isOutermost then ownEligible
360
360
else combineEligibles(ownEligible, outerImplicits.nn.uncachedEligible(tp))
361
361
362
362
/** The implicit references that are eligible for type `tp`. */
@@ -383,7 +383,7 @@ object Implicits:
383
383
private def computeEligible (tp : Type ): List [Candidate ] = /* >|>*/ trace(i " computeEligible $tp in $refs%, % " , implicitsDetailed) /* <|<*/ {
384
384
if (monitored) record(s " check eligible refs in irefCtx " , refs.length)
385
385
val ownEligible = filterMatching(tp)
386
- if isOuterMost then ownEligible
386
+ if isOutermost then ownEligible
387
387
else combineEligibles(ownEligible, outerImplicits.nn.eligible(tp))
388
388
}
389
389
@@ -392,7 +392,7 @@ object Implicits:
392
392
393
393
override def toString : String = {
394
394
val own = i " (implicits: $refs%, %) "
395
- if (isOuterMost ) own else own + " \n " + outerImplicits
395
+ if (isOutermost ) own else own + " \n " + outerImplicits
396
396
}
397
397
398
398
/** This context, or a copy, ensuring root import from symbol `root`
@@ -1550,34 +1550,117 @@ trait Implicits:
1550
1550
case _ =>
1551
1551
tp.isAny || tp.isAnyRef
1552
1552
1553
- private def searchImplicit (contextual : Boolean ): SearchResult =
1553
+ /** Search implicit in context `ctxImplicits` or else in implicit scope
1554
+ * of expected type if `ctxImplicits == null`.
1555
+ */
1556
+ private def searchImplicit (ctxImplicits : ContextualImplicits | Null ): SearchResult =
1554
1557
if isUnderspecified(wildProto) then
1555
1558
SearchFailure (TooUnspecific (pt), span)
1556
1559
else
1557
- val eligible =
1560
+ val contextual = ctxImplicits != null
1561
+ val preEligible = // the eligible candidates, ignoring positions
1558
1562
if contextual then
1559
1563
if ctx.gadt.isNarrowing then
1560
1564
withoutMode(Mode .ImplicitsEnabled ) {
1561
1565
ctx.implicits.uncachedEligible(wildProto)
1562
1566
}
1563
1567
else ctx.implicits.eligible(wildProto)
1564
1568
else implicitScope(wildProto).eligible
1565
- searchImplicit(eligible, contextual) match
1569
+
1570
+ /** Does candidate `cand` come too late for it to be considered as an
1571
+ * eligible candidate? This is the case if `cand` appears in the same
1572
+ * scope as a given definition of the form `given ... = ...` that
1573
+ * encloses the search point and `cand` comes later in the source or
1574
+ * coincides with that given definition.
1575
+ */
1576
+ def comesTooLate (cand : Candidate ): Boolean =
1577
+ val candSym = cand.ref.symbol
1578
+ def candSucceedsGiven (sym : Symbol ): Boolean =
1579
+ val owner = sym.owner
1580
+ if owner == candSym.owner then
1581
+ sym.is(GivenVal ) && sym.span.exists && sym.span.start <= candSym.span.start
1582
+ else if owner.isClass then false
1583
+ else candSucceedsGiven(owner)
1584
+
1585
+ ctx.isTyper
1586
+ && ! candSym.isOneOf(TermParamOrAccessor | Synthetic )
1587
+ && candSym.span.exists
1588
+ && candSucceedsGiven(ctx.owner)
1589
+ end comesTooLate
1590
+
1591
+ val eligible = // the eligible candidates that come before the search point
1592
+ if contextual && sourceVersion.isAtLeast(SourceVersion .`3.4`)
1593
+ then preEligible.filterNot(comesTooLate)
1594
+ else preEligible
1595
+
1596
+ def checkResolutionChange (result : SearchResult ) =
1597
+ if (eligible ne preEligible)
1598
+ && ! Feature .enabled(Feature .givenLoopPrevention)
1599
+ then
1600
+ val prevResult = searchImplicit(preEligible, contextual)
1601
+ prevResult match
1602
+ case prevResult : SearchSuccess =>
1603
+ def remedy = pt match
1604
+ case _ : SelectionProto =>
1605
+ " conversion,\n - use an import to get extension method into scope"
1606
+ case _ : ViewProto =>
1607
+ " conversion"
1608
+ case _ =>
1609
+ " argument"
1610
+
1611
+ def showResult (r : SearchResult ) = r match
1612
+ case r : SearchSuccess => ctx.printer.toTextRef(r.ref).show
1613
+ case r => r.show
1614
+
1615
+ result match
1616
+ case result : SearchSuccess if prevResult.ref frozen_=:= result.ref =>
1617
+ // OK
1618
+ case _ =>
1619
+ val msg =
1620
+ em """ Result of implicit search for $pt will change.
1621
+ |Current result ${showResult(prevResult)} will be no longer eligible
1622
+ | because it is not defined before the search position.
1623
+ |Result with new rules: ${showResult(result)}.
1624
+ |To opt into the new rules, use the `experimental.givenLoopPrevention` language import.
1625
+ |
1626
+ |To fix the problem without the language import, you could try one of the following:
1627
+ | - use a `given ... with` clause as the enclosing given,
1628
+ | - rearrange definitions so that ${showResult(prevResult)} comes earlier,
1629
+ | - use an explicit $remedy. """
1630
+ if sourceVersion.isAtLeast(SourceVersion .`3.5`)
1631
+ then report.error(msg, srcPos)
1632
+ else report.warning(msg.append(" \n This will be an error in Scala 3.5 and later." ), srcPos)
1633
+ case _ =>
1634
+ prevResult
1635
+ else result
1636
+ end checkResolutionChange
1637
+
1638
+ val result = searchImplicit(eligible, contextual)
1639
+ result match
1566
1640
case result : SearchSuccess =>
1567
- result
1641
+ checkResolutionChange( result)
1568
1642
case failure : SearchFailure =>
1569
1643
failure.reason match
1570
1644
case _ : AmbiguousImplicits => failure
1571
1645
case reason =>
1572
1646
if contextual then
1573
- searchImplicit(contextual = false ).recoverWith {
1574
- failure2 => failure2.reason match
1575
- case _ : AmbiguousImplicits => failure2
1576
- case _ =>
1577
- reason match
1578
- case (_ : DivergingImplicit ) => failure
1579
- case _ => List (failure, failure2).maxBy(_.tree.treeSize)
1580
- }
1647
+ // If we filtered out some candidates for being too late, we should
1648
+ // do another contextual search further out, since the dropped candidates
1649
+ // might have shadowed an eligible candidate in an outer level.
1650
+ // Otherwise, proceed with a search of the implicit scope.
1651
+ val newCtxImplicits =
1652
+ if eligible eq preEligible then null
1653
+ else ctxImplicits.nn.outerImplicits: ContextualImplicits | Null
1654
+ // !!! Dotty problem: without the ContextualImplicits | Null type ascription
1655
+ // we get a Ycheck failure after arrayConstructors due to "Types differ"
1656
+ checkResolutionChange :
1657
+ searchImplicit(newCtxImplicits).recoverWith:
1658
+ failure2 => failure2.reason match
1659
+ case _ : AmbiguousImplicits => failure2
1660
+ case _ =>
1661
+ reason match
1662
+ case (_ : DivergingImplicit ) => failure
1663
+ case _ => List (failure, failure2).maxBy(_.tree.treeSize)
1581
1664
else failure
1582
1665
end searchImplicit
1583
1666
@@ -1595,7 +1678,7 @@ trait Implicits:
1595
1678
case ref : TermRef =>
1596
1679
SearchSuccess (tpd.ref(ref).withSpan(span.startPos), ref, 0 )(ctx.typerState, ctx.gadt)
1597
1680
case _ =>
1598
- searchImplicit(contextual = true )
1681
+ searchImplicit(ctx.implicits )
1599
1682
end bestImplicit
1600
1683
1601
1684
def implicitScope (tp : Type ): OfTypeImplicits = ctx.run.nn.implicitScope(tp)
0 commit comments