Skip to content

Commit 306a0f0

Browse files
committed
Fix implicit cache
1 parent bd2f51d commit 306a0f0

File tree

4 files changed

+19
-30
lines changed

4 files changed

+19
-30
lines changed

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ object Implicits:
300300
*/
301301
class ContextualImplicits(val refs: List[ImplicitRef], val outerImplicits: ContextualImplicits)(initctx: Context) extends ImplicitRefs(initctx) {
302302
private val eligibleCache = EqHashMap[Type, List[Candidate]]()
303+
private val eligibleCacheUnsafeNulls = EqHashMap[Type, List[Candidate]]()
303304

304305
/** The level increases if current context has a different owner or scope than
305306
* the context of the next-outer ImplicitRefs. This is however disabled under
@@ -324,24 +325,26 @@ object Implicits:
324325

325326
/** The implicit references that are eligible for type `tp`. */
326327
def eligible(tp: Type, enableUnsafeNulls: Boolean = false): List[Candidate] =
327-
if (tp.hash == NotCached)
328-
Stats.record(i"compute eligible not cached ${tp.getClass}")
329-
Stats.record(i"compute eligible not cached")
330-
computeEligible(tp, enableUnsafeNulls)
331-
else {
332-
val eligibles = eligibleCache.lookup(tp)
333-
if (eligibles != null) {
328+
def searchWithCache(cache: EqHashMap[Type, List[Candidate]]) = {
329+
val eligibles = cache.lookup(tp)
330+
if eligibles != null then
334331
Stats.record("cached eligible")
335332
eligibles
336-
}
337-
else if (irefCtx eq NoContext) Nil
338-
else {
333+
else if irefCtx eq NoContext then Nil
334+
else
339335
Stats.record(i"compute eligible cached")
340336
val result = computeEligible(tp, enableUnsafeNulls)
341-
eligibleCache(tp) = result
337+
cache(tp) = result
342338
result
343-
}
344339
}
340+
if tp.hash == NotCached then
341+
Stats.record(i"compute eligible not cached ${tp.getClass}")
342+
Stats.record(i"compute eligible not cached")
343+
computeEligible(tp, enableUnsafeNulls)
344+
else if enableUnsafeNulls then
345+
searchWithCache(eligibleCacheUnsafeNulls)
346+
else
347+
searchWithCache(eligibleCache)
345348

346349
private def computeEligible(tp: Type, enableUnsafeNulls: Boolean): List[Candidate] = /*>|>*/ trace(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ {
347350
if (monitored) record(s"check eligible refs in irefCtx", refs.length)

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3548,18 +3548,7 @@ class Typer extends Namer
35483548
report.error(em"the result of an implicit conversion must be more specific than $pt", tree.srcPos)
35493549
tree.cast(pt)
35503550
else
3551-
// TODO: we don't need to second time search, need to modify Implicits more
3552-
def normalSearch =
3553-
searchTree(tree)(failure => tryUnsafeNullConver(cannotFind(failure)))
3554-
treeTpe match {
3555-
case OrNull(tpe1) if ctx.mode.is(Mode.UnsafeNullConversion) =>
3556-
// If the type of the tree is nullable, and unsafeNullConversion is enabled,
3557-
// then we search the tree without the `Null` type first.
3558-
// If this fails, we search the original tree.
3559-
searchTree(tree.cast(tpe1)) { _ => normalSearch }
3560-
case _ =>
3561-
normalSearch
3562-
}
3551+
searchTree(tree)(failure => tryUnsafeNullConver(cannotFind(failure)))
35633552
else tryUnsafeNullConver(recover(NoMatchingImplicits))
35643553
}
35653554
}

docs/docs/internals/explicit-nulls.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,7 @@ The reason to use the `UnsafeNullConversion` mode is because the current context
9494

9595
Since we want to allow selecting member on nullable values, when searching a member of a type, the `| Null` part should be ignored. See `goOr` in `Types.scala`.
9696

97-
During adapting, if the type of the tree is not a subtype of the expected type, the `adaptToSubType` in `Typer.scala` will run. The implicit search is invoked to find conversions for the tree. If `unsafeNulls` is enabled, we use the new search scheme:
98-
1. If the `tree.tpe` is nullable, we strip `Null` from the tree then search.
99-
2. If the `tree.tpe` is not nullable or the last step fails, we search on the tree directly.
100-
3. If the last step fails, we try to cast tree to `pt` if the two types `isUnsafelyConvertable`.
101-
102-
Since implicit search (finding candidates and trying to type the new tree) could run in some different contexts, we have to pass the `UnsafeNullConversion` mode to the search context.
97+
During adapting, if the type of the tree is not a subtype of the expected type, the `adaptToSubType` in `Typer.scala` will run. The implicit search is invoked to find conversions for the tree. Since implicit search (finding candidates and trying to type the new tree) could run in some different contexts, we have to pass the `UnsafeNullConversion` mode to the search context.
10398

10499
The SAM type conversion also happens in `adaptToSubType`. We need to strip `Null` from `pt` in order to get class information.
105100

tests/explicit-nulls/neg/unsafe-scope.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ class S {
66

77
val x: String = s // error
88
val xs: Array[String | Null] = s // error
9+
910
{
1011
import scala.language.unsafeNulls
12+
// ensure the previous search cache is not used here
1113
val y: String = s
1214
val ys: Array[String | Null] = s
1315
}

0 commit comments

Comments
 (0)