Skip to content

Commit

Permalink
Scala 3 migration warning for implicits found in package prefix
Browse files Browse the repository at this point in the history
The implicit scope in Scala 3 no longer includes the requested
type's package prefix
  • Loading branch information
lrytz committed Dec 4, 2023
1 parent b275b38 commit adf5dbb
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 22 deletions.
8 changes: 5 additions & 3 deletions src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1095,9 +1095,9 @@ trait Contexts { self: Analyzer =>
f(imported)
}

private def collectImplicits(syms: Scope, pre: Type, imported: Boolean = false): List[ImplicitInfo] =
private def collectImplicits(syms: Scope, pre: Type, imported: Boolean = false, inPackagePrefix: Boolean = false): List[ImplicitInfo] =
for (sym <- syms.toList if isQualifyingImplicit(sym.name, sym, pre, imported)) yield
new ImplicitInfo(sym.name, pre, sym)
new ImplicitInfo(sym.name, pre, sym, inPackagePrefix)

private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = if (isExcludedRootImport(imp)) List() else {
val qual = imp.qual
Expand Down Expand Up @@ -1189,7 +1189,9 @@ trait Contexts { self: Analyzer =>
} else if (owner.isPackageClass) {
// the corresponding package object may contain implicit members.
val pre = owner.packageObject.typeOfThis
Some(collectImplicits(pre.implicitMembers, pre))
// `inPackagePrefix = false` because the implicit is in an enclosing package of the use site, not in
// the path of the implicit's type. See t12919.scala.
Some(collectImplicits(pre.implicitMembers, pre, inPackagePrefix = false))
} else SomeOfNil
}

Expand Down
48 changes: 29 additions & 19 deletions src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,19 @@ trait Implicits extends splain.SplainData {
if (settings.areStatisticsEnabled) statistics.stopCounter(findMemberImpl, findMemberStart)
if (settings.areStatisticsEnabled) statistics.stopCounter(subtypeImpl, subtypeStart)

if (result.isSuccess && settings.lintImplicitRecursion && result.tree.symbol != null) {
val s =
if (result.tree.symbol.isAccessor) result.tree.symbol.accessed
else if (result.tree.symbol.isModule) result.tree.symbol.moduleClass
else result.tree.symbol
if (s != NoSymbol && context.owner.hasTransOwner(s))
context.warning(result.tree.pos, s"Implicit resolves to enclosing ${result.tree.symbol}", WarningCategory.WFlagSelfImplicit)
if (result.isSuccess && result.tree.symbol != null) {
if (settings.lintImplicitRecursion) {
val s =
if (result.tree.symbol.isAccessor) result.tree.symbol.accessed
else if (result.tree.symbol.isModule) result.tree.symbol.moduleClass
else result.tree.symbol
if (s != NoSymbol && context.owner.hasTransOwner(s))
context.warning(result.tree.pos, s"Implicit resolves to enclosing ${result.tree.symbol}", WarningCategory.WFlagSelfImplicit)
}

if (result.inPackagePrefix && currentRun.isScala3) {
context.warning(result.tree.pos, s"Implicit `${result.tree.symbol.fullNameString}` was found in package prefix of the required type, which is not part of the implicit search scope in Scala 3", WarningCategory.Scala3Migration)
}
}
implicitSearchContext.emitImplicitDictionary(result)
}
Expand Down Expand Up @@ -196,9 +202,9 @@ trait Implicits extends splain.SplainData {
* that were instantiated by the winning implicit.
* @param undetparams undetermined type parameters
*/
class SearchResult(val tree: Tree, val subst: TreeTypeSubstituter, val undetparams: List[Symbol]) {
override def toString = "SearchResult(%s, %s)".format(tree,
if (subst.isEmpty) "" else subst)
class SearchResult(val tree: Tree, val subst: TreeTypeSubstituter, val undetparams: List[Symbol], val inPackagePrefix: Boolean = false) {
override def toString = "SearchResult(%s, %s, %s)".format(tree,
if (subst.isEmpty) "" else subst, inPackagePrefix)

def isFailure = false
def isAmbiguousFailure = false
Expand All @@ -225,7 +231,7 @@ trait Implicits extends splain.SplainData {
* @param pre The prefix type of the implicit
* @param sym The symbol of the implicit
*/
class ImplicitInfo(val name: Name, val pre: Type, val sym: Symbol) {
class ImplicitInfo(val name: Name, val pre: Type, val sym: Symbol, val inPackagePrefix: Boolean = false) {
private[this] var tpeCache: Type = null
private[this] var depolyCache: Type = null
private[this] var isErroneousCache: TriState = TriState.Unknown
Expand Down Expand Up @@ -294,7 +300,8 @@ trait Implicits extends splain.SplainData {
case that: ImplicitInfo =>
this.name == that.name &&
this.pre =:= that.pre &&
this.sym == that.sym
this.sym == that.sym &&
this.inPackagePrefix == that.inPackagePrefix
case _ => false
}
override def hashCode = {
Expand Down Expand Up @@ -605,7 +612,7 @@ trait Implicits extends splain.SplainData {
} else {
val ref = context.refByNameImplicit(pt)
if(ref != EmptyTree)
new SearchResult(ref, EmptyTreeTypeSubstituter, Nil)
new SearchResult(ref, EmptyTreeTypeSubstituter, Nil, inPackagePrefix = info.inPackagePrefix)
else {
@tailrec
def loop(ois: List[OpenImplicit], isByName: Boolean): Option[OpenImplicit] =
Expand All @@ -620,7 +627,7 @@ trait Implicits extends splain.SplainData {
recursiveImplicit match {
case Some(rec) =>
val ref = atPos(pos.focus)(context.linkByNameImplicit(rec.pt))
new SearchResult(ref, EmptyTreeTypeSubstituter, Nil)
new SearchResult(ref, EmptyTreeTypeSubstituter, Nil, inPackagePrefix = info.inPackagePrefix)
case None =>
try {
context.openImplicits = OpenImplicit(info, pt, tree, isView, isByNamePt) :: context.openImplicits
Expand Down Expand Up @@ -963,7 +970,7 @@ trait Implicits extends splain.SplainData {
splainPushImplicitSearchFailure(itree3, pt, err)
fail("typing TypeApply reported errors for the implicit tree: " + err.errMsg)
case None =>
val result = new SearchResult(unsuppressMacroExpansion(itree3), subst, context.undetparams)
val result = new SearchResult(unsuppressMacroExpansion(itree3), subst, context.undetparams, inPackagePrefix = info.inPackagePrefix)
if (settings.areStatisticsEnabled) statistics.incCounter(foundImplicits)
typingLog("success", s"inferred value of type $ptInstantiated is $result")
result
Expand Down Expand Up @@ -1328,10 +1335,13 @@ trait Implicits extends splain.SplainData {
if (symInfos.exists(_.isSearchedPrefix))
infoMap(sym) = SearchedPrefixImplicitInfo(pre) :: symInfos
else if (pre.isStable && !pre.typeSymbol.isExistentiallyBound) {
val pre1 =
if (sym.isPackageClass) sym.packageObject.typeOfThis
else singleType(pre, companionSymbolOf(sym, context))
val preInfos = pre1.implicitMembers.iterator.map(mem => new ImplicitInfo(mem.name, pre1, mem))
val (pre1, inPackagePrefix) =
if (sym.isPackageClass) (sym.packageObject.typeOfThis, true)
else (singleType(pre, companionSymbolOf(sym, context)), false)
val preInfos = {
if (currentRun.isScala3Cross && inPackagePrefix) Iterator.empty
else pre1.implicitMembers.iterator.map(mem => new ImplicitInfo(mem.name, pre1, mem, inPackagePrefix = inPackagePrefix))
}
val mergedInfos = if (symInfos.isEmpty) preInfos else {
if (shouldLogAtThisPhase && symInfos.exists(!_.dependsOnPrefix)) log {
val nonDepInfos = symInfos.iterator.filterNot(_.dependsOnPrefix).mkString("(", ", ", ")")
Expand Down
4 changes: 4 additions & 0 deletions test/files/neg/t12919-3cross.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
t12919-3cross.scala:23: error: No implicit Ordering defined for a.A.
def f(xs: List[a.A]) = xs.sorted // not found
^
1 error
35 changes: 35 additions & 0 deletions test/files/neg/t12919-3cross.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// scalac: -Xsource:3cross

package object a {
implicit val aOrd: Ordering[A] = null
implicit val bOrd: Ordering[b.B] = null
}

package a {
class A

package aa {
class U {
def f(xs: List[a.A]) = xs.sorted // ok
def g(xs: List[b.B]) = xs.sorted // ok
}
}
}

package b {
class B

class V {
def f(xs: List[a.A]) = xs.sorted // not found
}
}

package c {
import a._

class W {
def f(xs: List[a.A]) = xs.sorted // ok
def g(xs: List[b.B]) = xs.sorted // ok
}
}

6 changes: 6 additions & 0 deletions test/files/neg/t12919.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
t12919.scala:23: error: Implicit `a.aOrd` was found in package prefix of the required type, which is not part of the implicit search scope in Scala 3
Scala 3 migration messages are errors under -Xsource:3. Use -Wconf / @nowarn to filter them or add -Xmigration to demote them to warnings.
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=scala3-migration, site=b.V.f
def f(xs: List[a.A]) = xs.sorted // warn
^
1 error
35 changes: 35 additions & 0 deletions test/files/neg/t12919.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// scalac: -Xsource:3 -Werror

package object a {
implicit val aOrd: Ordering[A] = null
implicit val bOrd: Ordering[b.B] = null
}

package a {
class A

package aa {
class U {
def f(xs: List[a.A]) = xs.sorted // ok
def g(xs: List[b.B]) = xs.sorted // ok
}
}
}

package b {
class B

class V {
def f(xs: List[a.A]) = xs.sorted // warn
}
}

package c {
import a._

class W {
def f(xs: List[a.A]) = xs.sorted // ok
def g(xs: List[b.B]) = xs.sorted // ok
}
}

0 comments on commit adf5dbb

Please sign in to comment.