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 Jan 18, 2024
1 parent b353f1d commit 872fd69
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 21 deletions.
12 changes: 7 additions & 5 deletions src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1096,9 +1096,9 @@ trait Contexts { self: Analyzer =>
f(imported)
}

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

private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = if (isExcludedRootImport(imp)) List() else {
val qual = imp.qual
Expand All @@ -1114,12 +1114,12 @@ trait Contexts { self: Analyzer =>
// Looking up implicit members in the package, rather than package object, here is at least
// consistent with what is done just below for named imports.
for (sym <- qual.tpe.implicitMembers.toList if isQualifyingImplicit(sym.name, sym, pre, imported = true))
yield new ImplicitInfo(sym.name, pre, sym, imp, sel)
yield new ImplicitInfo(sym.name, pre, sym, importInfo = imp, importSelector = sel)
case (sel @ ImportSelector(from, _, to, _)) :: sels1 =>
var impls = collect(sels1).filter(_.name != from)
if (!sel.isMask)
withQualifyingImplicitAlternatives(imp, to, pre) { sym =>
impls = new ImplicitInfo(to, pre, sym, imp, sel) :: impls
impls = new ImplicitInfo(to, pre, sym, importInfo = imp, importSelector = sel) :: impls
}
impls
}
Expand Down Expand Up @@ -1191,7 +1191,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
45 changes: 29 additions & 16 deletions src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,20 @@ 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) {
if (result.isSuccess && result.tree.symbol != null) {
val rts = result.tree.symbol
val s = if (rts.isAccessor) rts.accessed else if (rts.isModule) rts.moduleClass else rts
if (s != NoSymbol && context.owner.hasTransOwner(s))
context.warning(result.tree.pos, s"Implicit resolves to enclosing $rts", WarningCategory.WFlagSelfImplicit)
if (settings.lintImplicitRecursion) {
val s = if (rts.isAccessor) rts.accessed else if (rts.isModule) rts.moduleClass else rts
if (s != NoSymbol && context.owner.hasTransOwner(s))
context.warning(result.tree.pos, s"Implicit resolves to enclosing $rts", WarningCategory.WFlagSelfImplicit)
}

if (result.inPackagePrefix && currentRun.isScala3) {
val msg =
s"""Implicit $rts was found in a package prefix of the required type, which is not part of the implicit scope in Scala 3.
|For migration, add `import ${rts.fullNameString}`.""".stripMargin
context.warning(result.tree.pos, msg, WarningCategory.Scala3Migration)
}
}
implicitSearchContext.emitImplicitDictionary(result)
}
Expand Down Expand Up @@ -194,8 +203,8 @@ 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], val implicitInfo: ImplicitInfo = null) {
override def toString = s"SearchResult($tree, ${if (subst.isEmpty) "" else subst})"
class SearchResult(val tree: Tree, val subst: TreeTypeSubstituter, val undetparams: List[Symbol], val inPackagePrefix: Boolean = false, val implicitInfo: ImplicitInfo = null) {
override def toString = s"SearchResult($tree, ${if (subst.isEmpty) "" else subst}, $inPackagePrefix)"

def isFailure = false
def isAmbiguousFailure = false
Expand All @@ -222,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, val importInfo: ImportInfo = null, val importSelector: ImportSelector = null) {
class ImplicitInfo(val name: Name, val pre: Type, val sym: Symbol, val inPackagePrefix: Boolean = false, val importInfo: ImportInfo = null, val importSelector: ImportSelector = null) {
private[this] var tpeCache: Type = null
private[this] var depolyCache: Type = null
private[this] var isErroneousCache: TriState = TriState.Unknown
Expand Down Expand Up @@ -291,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 @@ -602,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 @@ -617,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 @@ -957,7 +967,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 @@ -1205,7 +1215,7 @@ trait Implicits extends splain.SplainData {
val res = typedImplicit(firstPending, ptChecked = true, isLocalToCallsite)
if (res.isFailure) res
else
new SearchResult(res.tree, res.subst, res.undetparams, firstPending)
new SearchResult(res.tree, res.subst, res.undetparams, implicitInfo = firstPending)
}
else SearchFailure
} finally {
Expand Down Expand Up @@ -1328,10 +1338,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
}
}

7 changes: 7 additions & 0 deletions test/files/neg/t12919.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
t12919.scala:23: error: Implicit value aOrd was found in a package prefix of the required type, which is not part of the implicit scope in Scala 3.
For migration, add `import a.aOrd`.
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 872fd69

Please sign in to comment.