Skip to content

Commit 9129cfe

Browse files
committed
SI-4270 Disqualify in scope implicits that are shadowed.
If an expression wouldn't type check explicitly, it shouldn't be allowed implicitly. Employs typedIdent, which already does this sort of thing rather well, instead of continuing to reimplement it in Implicits. Remove check for non-implicit synonym, which is subsumed by typing an Ident. Workaround Scaladoc oddity, by using an attributed select when the context is deficient.
1 parent 0dea3d5 commit 9129cfe

File tree

6 files changed

+100
-49
lines changed

6 files changed

+100
-49
lines changed

src/compiler/scala/tools/nsc/typechecker/Contexts.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ trait Contexts { self: Analyzer =>
100100
var outer: Context = _ // The next outer context
101101
var enclClass: Context = _ // The next outer context whose tree is a
102102
// template or package definition
103+
@inline final def savingEnclClass[A](c: Context)(a: => A): A = {
104+
val saved = enclClass
105+
enclClass = c
106+
try a finally enclClass = saved
107+
}
108+
103109
var enclMethod: Context = _ // The next outer context whose tree is a method
104110
var variance: Int = _ // Variance relative to enclosing class
105111
private var _undetparams: List[Symbol] = List() // Undetermined type parameters,
@@ -638,11 +644,12 @@ trait Contexts { self: Analyzer =>
638644
if (owner != nextOuter.owner && owner.isClass && !owner.isPackageClass && !inSelfSuperCall) {
639645
if (!owner.isInitialized) return nextOuter.implicitss
640646
// debuglog("collect member implicits " + owner + ", implicit members = " + owner.thisType.implicitMembers)//DEBUG
641-
val savedEnclClass = enclClass
642-
this.enclClass = this
643-
val res = collectImplicits(owner.thisType.implicitMembers, owner.thisType)
644-
this.enclClass = savedEnclClass
645-
res
647+
savingEnclClass(this) {
648+
// !!! In the body of `class C(implicit a: A) { }`, `implicitss` returns `List(List(a), List(a), List(<predef..)))`
649+
// it handled correctly by implicit search, which considers the second `a` to be shadowed, but should be
650+
// remedied nonetheless.
651+
collectImplicits(owner.thisType.implicitMembers, owner.thisType)
652+
}
646653
} else if (scope != nextOuter.scope && !owner.isPackageClass) {
647654
debuglog("collect local implicits " + scope.toList)//DEBUG
648655
collectImplicits(scope.toList, NoPrefix)

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,10 @@ trait Implicits {
390390
* Detect infinite search trees for implicits.
391391
*
392392
* @param info The given implicit info describing the implicit definition
393+
* @param isLocal Is the implicit in the local scope of the call site?
393394
* @pre `info.tpe` does not contain an error
394395
*/
395-
private def typedImplicit(info: ImplicitInfo, ptChecked: Boolean): SearchResult = {
396+
private def typedImplicit(info: ImplicitInfo, ptChecked: Boolean, isLocal: Boolean): SearchResult = {
396397
(context.openImplicits find { case (tp, tree1) => tree1.symbol == tree.symbol && dominates(pt, tp)}) match {
397398
case Some(pending) =>
398399
//println("Pending implicit "+pending+" dominates "+pt+"/"+undetParams) //@MDEBUG
@@ -401,7 +402,7 @@ trait Implicits {
401402
try {
402403
context.openImplicits = (pt, tree) :: context.openImplicits
403404
// println(" "*context.openImplicits.length+"typed implicit "+info+" for "+pt) //@MDEBUG
404-
typedImplicit0(info, ptChecked)
405+
typedImplicit0(info, ptChecked, isLocal)
405406
} catch {
406407
case ex: DivergentImplicit =>
407408
//println("DivergentImplicit for pt:"+ pt +", open implicits:"+context.openImplicits) //@MDEBUG
@@ -534,7 +535,7 @@ trait Implicits {
534535
case _ => false
535536
}
536537

537-
private def typedImplicit0(info: ImplicitInfo, ptChecked: Boolean): SearchResult = {
538+
private def typedImplicit0(info: ImplicitInfo, ptChecked: Boolean, isLocal: Boolean): SearchResult = {
538539
incCounter(plausiblyCompatibleImplicits)
539540
printTyping (
540541
ptBlock("typedImplicit0",
@@ -549,17 +550,24 @@ trait Implicits {
549550
)
550551

551552
if (ptChecked || matchesPt(info))
552-
typedImplicit1(info)
553+
typedImplicit1(info, isLocal)
553554
else
554555
SearchFailure
555556
}
556557

557-
private def typedImplicit1(info: ImplicitInfo): SearchResult = {
558+
private def typedImplicit1(info: ImplicitInfo, isLocal: Boolean): SearchResult = {
558559
incCounter(matchingImplicits)
559560

560561
val itree = atPos(pos.focus) {
561-
if (info.pre == NoPrefix) Ident(info.name)
562-
else {
562+
// workaround for deficient context provided by ModelFactoryImplicitSupport#makeImplicitConstraints
563+
val isScalaDoc = context.tree == EmptyTree
564+
565+
if (isLocal && !isScalaDoc) {
566+
// SI-4270 SI-5376 Always use an unattributed Ident for implicits in the local scope,
567+
// rather than an attributed Select, to detect shadowing.
568+
Ident(info.name)
569+
} else {
570+
assert(info.pre != NoPrefix, info)
563571
// SI-2405 Not info.name, which might be an aliased import
564572
val implicitMemberName = info.sym.name
565573
Select(gen.mkAttributedQualifier(info.pre), implicitMemberName)
@@ -607,8 +615,8 @@ trait Implicits {
607615

608616
if (context.hasErrors)
609617
fail("hasMatchingSymbol reported threw error(s)")
610-
else if (!hasMatchingSymbol(itree1))
611-
fail("candidate implicit %s is shadowed by other implicit %s".format(
618+
else if (isLocal && !hasMatchingSymbol(itree1))
619+
fail("candidate implicit %s is shadowed by %s".format(
612620
info.sym.fullLocationString, itree1.symbol.fullLocationString))
613621
else {
614622
val tvars = undetParams map freshVar
@@ -683,17 +691,6 @@ trait Implicits {
683691
}
684692
}
685693

686-
// #3453: in addition to the implicit symbols that may shadow the implicit with
687-
// name `name`, this method tests whether there's a non-implicit symbol with name
688-
// `name` in scope. Inspired by logic in typedIdent.
689-
private def nonImplicitSynonymInScope(name: Name) = {
690-
// the implicit ones are handled by the `shadowed` set above
691-
context.scope.lookupEntry(name) match {
692-
case x: ScopeEntry => reallyExists(x.sym) && !x.sym.isImplicit
693-
case _ => false
694-
}
695-
}
696-
697694
/** Should implicit definition symbol `sym` be considered for applicability testing?
698695
* This is the case if one of the following holds:
699696
* - the symbol's type is initialized
@@ -737,14 +734,15 @@ trait Implicits {
737734
/** Prune ImplicitInfos down to either all the eligible ones or the best one.
738735
*
739736
* @param iss list of list of infos
740-
* @param shadowed set in which to record names that are shadowed by implicit infos
741-
* If it is null, no shadowing.
737+
* @param isLocal if true, `iss` represents in-scope implicits, which must respect the normal rules of
738+
* shadowing. The head of the list `iss` must represent implicits from the closest
739+
* enclosing scope, and so on.
742740
*/
743-
class ImplicitComputation(iss: Infoss, shadowed: util.HashSet[Name]) {
741+
class ImplicitComputation(iss: Infoss, isLocal: Boolean) {
742+
private val shadowed = util.HashSet[Name](512)
744743
private var best: SearchResult = SearchFailure
745744
private def isShadowed(name: Name) = (
746-
(shadowed != null)
747-
&& (shadowed(name) || nonImplicitSynonymInScope(name))
745+
isLocal && shadowed(name)
748746
)
749747
private def isIneligible(info: ImplicitInfo) = (
750748
info.isCyclicOrErroneous
@@ -788,9 +786,7 @@ trait Implicits {
788786
val eligible = {
789787
val matches = iss flatMap { is =>
790788
val result = is filter (info => checkValid(info.sym) && survives(info))
791-
if (shadowed ne null)
792-
shadowed addEntries (is map (_.name))
793-
789+
if (isLocal) shadowed addEntries (is map (_.name))
794790
result
795791
}
796792

@@ -812,7 +808,7 @@ trait Implicits {
812808
case Nil => acc
813809
case i :: is =>
814810
def tryImplicitInfo(i: ImplicitInfo) =
815-
try typedImplicit(i, true)
811+
try typedImplicit(i, ptChecked = true, isLocal)
816812
catch divergenceHandler
817813

818814
tryImplicitInfo(i) match {
@@ -842,7 +838,7 @@ trait Implicits {
842838

843839
/** Returns all eligible ImplicitInfos and their SearchResults in a map.
844840
*/
845-
def findAll() = mapFrom(eligible)(typedImplicit(_, false))
841+
def findAll() = mapFrom(eligible)(typedImplicit(_, ptChecked = false, isLocal))
846842

847843
/** Returns the SearchResult of the best match.
848844
*/
@@ -891,7 +887,7 @@ trait Implicits {
891887
*/
892888
def applicableInfos(iss: Infoss, isLocal: Boolean): Map[ImplicitInfo, SearchResult] = {
893889
val start = startCounter(subtypeAppInfos)
894-
val computation = new ImplicitComputation(iss, if (isLocal) util.HashSet[Name](512) else null) { }
890+
val computation = new ImplicitComputation(iss, isLocal) { }
895891
val applicable = computation.findAll()
896892

897893
stopCounter(subtypeAppInfos, start)
@@ -909,7 +905,7 @@ trait Implicits {
909905
*/
910906
def searchImplicit(implicitInfoss: Infoss, isLocal: Boolean): SearchResult =
911907
if (implicitInfoss.forall(_.isEmpty)) SearchFailure
912-
else new ImplicitComputation(implicitInfoss, if (isLocal) util.HashSet[Name](128) else null) findBest()
908+
else new ImplicitComputation(implicitInfoss, isLocal) findBest()
913909

914910
/** Produce an implicict info map, i.e. a map from the class symbols C of all parts of this type to
915911
* the implicit infos in the companion objects of these class symbols C.
@@ -1397,19 +1393,22 @@ trait Implicits {
13971393
def allImplicitsPoly(tvars: List[TypeVar]): List[(SearchResult, List[TypeConstraint])] = {
13981394
def resetTVars() = tvars foreach { _.constr = new TypeConstraint }
13991395

1400-
def eligibleInfos(iss: Infoss, isLocal: Boolean) = new ImplicitComputation(iss, if (isLocal) util.HashSet[Name](512) else null).eligible
1401-
val allEligibleInfos = (eligibleInfos(context.implicitss, true) ++ eligibleInfos(implicitsOfExpectedType, false)).toList
1402-
1403-
allEligibleInfos flatMap { ii =>
1404-
// each ImplicitInfo contributes a distinct set of constraints (generated indirectly by typedImplicit)
1405-
// thus, start each type var off with a fresh for every typedImplicit
1406-
resetTVars()
1407-
// any previous errors should not affect us now
1408-
context.flushBuffer()
1409-
val res = typedImplicit(ii, false)
1410-
if (res.tree ne EmptyTree) List((res, tvars map (_.constr)))
1411-
else Nil
1396+
def eligibleInfos(iss: Infoss, isLocal: Boolean) = {
1397+
val eligible = new ImplicitComputation(iss, isLocal).eligible
1398+
eligible.toList.flatMap {
1399+
(ii: ImplicitInfo) =>
1400+
// each ImplicitInfo contributes a distinct set of constraints (generated indirectly by typedImplicit)
1401+
// thus, start each type var off with a fresh for every typedImplicit
1402+
resetTVars()
1403+
// any previous errors should not affect us now
1404+
context.flushBuffer()
1405+
1406+
val res = typedImplicit(ii, ptChecked = false, isLocal)
1407+
if (res.tree ne EmptyTree) List((res, tvars map (_.constr)))
1408+
else Nil
1409+
}
14121410
}
1411+
eligibleInfos(context.implicitss, isLocal = true) ++ eligibleInfos(implicitsOfExpectedType, isLocal = false)
14131412
}
14141413
}
14151414

test/files/neg/t4270.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
t4270.scala:5: error: could not find implicit value for parameter e: Int
2+
implicitly[Int]
3+
^
4+
one error found

test/files/neg/t4270.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Test1 {
2+
object A { implicit val x: Int = 1 }
3+
import A.x
4+
def x: Int = 0
5+
implicitly[Int]
6+
}

test/files/neg/t5376.check

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
t5376.scala:12: error: type mismatch;
2+
found : String("a")
3+
required: Int
4+
"a": Int
5+
^
6+
t5376.scala:22: error: type mismatch;
7+
found : String("a")
8+
required: Int
9+
"a": Int
10+
^
11+
two errors found

test/files/neg/t5376.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object Test {
2+
object O1 { implicit def f(s: String): Int = 1 }
3+
object O2 { implicit def f(s: String): Int = 2 }
4+
object O3 { def f(s: String): Int = 3 }
5+
6+
// Import two implicits with the same name in the same scope.
7+
def m1 = {
8+
import O1._
9+
import O2._
10+
11+
// Implicit usage compiles.
12+
"a": Int
13+
}
14+
15+
// Import one implict and one non-implicit method with the
16+
// same name in the same scope.
17+
def m2 = {
18+
import O1._
19+
import O3._
20+
21+
// Implicit usage compiles.
22+
"a": Int
23+
}
24+
}

0 commit comments

Comments
 (0)