Skip to content

Commit ce1ce99

Browse files
authored
Select local implicits over name-imported over wildcard imported (#18203)
2 parents 9b06094 + ce9e6d6 commit ce1ce99

File tree

6 files changed

+354
-3
lines changed

6 files changed

+354
-3
lines changed

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ object Implicits:
299299
class ContextualImplicits(
300300
val refs: List[ImplicitRef],
301301
val outerImplicits: ContextualImplicits | Null,
302-
isImport: Boolean)(initctx: Context) extends ImplicitRefs(initctx) {
302+
val isImport: Boolean)(initctx: Context) extends ImplicitRefs(initctx) {
303303
private val eligibleCache = EqHashMap[Type, List[Candidate]]()
304304

305305
/** The level increases if current context has a different owner or scope than
@@ -330,8 +330,21 @@ object Implicits:
330330
if ownEligible.isEmpty then outerEligible
331331
else if outerEligible.isEmpty then ownEligible
332332
else
333-
val shadowed = ownEligible.map(_.ref.implicitName).toSet
334-
ownEligible ::: outerEligible.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))
333+
def filter(xs: List[Candidate], remove: List[Candidate]) =
334+
val shadowed = remove.map(_.ref.implicitName).toSet
335+
xs.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))
336+
337+
val outer = outerImplicits.uncheckedNN
338+
def isWildcardImport(using Context) = ctx.importInfo.nn.isWildcardImport
339+
def preferDefinitions = isImport && !outer.isImport
340+
def preferNamedImport = isWildcardImport && !isWildcardImport(using outer.irefCtx)
341+
342+
if !migrateTo3(using irefCtx) && level == outer.level && (preferDefinitions || preferNamedImport) then
343+
// special cases: definitions beat imports, and named imports beat
344+
// wildcard imports, provided both are in contexts with same scope
345+
filter(ownEligible, outerEligible) ::: outerEligible
346+
else
347+
ownEligible ::: filter(outerEligible, ownEligible)
335348

336349
def uncachedEligible(tp: Type)(using Context): List[Candidate] =
337350
Stats.record("uncached eligible")

tests/pos/i18183.migration.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// scalac: -source:3.0-migration
2+
3+
// A not-fully-minimal reproduction of the CI failure in http4s
4+
// While implementing the fix to name "shadowing" in implicit lookup.
5+
6+
import scala.util.control.NoStackTrace
7+
8+
final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
9+
def semiflatMap[D](f: B => F[D])(implicit F: Monad[F]): EitherT[F, A, D] = ???
10+
}
11+
12+
trait Applicative[F[_]] {
13+
def pure[A](x: A): F[A]
14+
}
15+
trait Monad[F[_]] extends Applicative[F]
16+
trait Async[F[_]] extends Monad[F]
17+
18+
final class Request[+F[_]]
19+
20+
final case class RequestCookie(name: String, content: String)
21+
22+
final class CSRF2[F[_], G[_]](implicit F: Async[F]) { self =>
23+
import CSRF2._
24+
25+
def signToken[M[_]](rawToken: String)(implicit F: Async[M]): M[CSRFToken] = ???
26+
27+
def refreshedToken[M[_]](implicit F: Async[M]): EitherT[M, CSRFCheckFailed, CSRFToken] =
28+
EitherT(extractRaw("")).semiflatMap(signToken[M])
29+
30+
def extractRaw[M[_]: Async](rawToken: String): M[Either[CSRFCheckFailed, String]] = ???
31+
}
32+
33+
object CSRF2 {
34+
type CSRFToken
35+
36+
case object CSRFCheckFailed extends Exception("CSRF Check failed") with NoStackTrace
37+
type CSRFCheckFailed = CSRFCheckFailed.type
38+
}

tests/run/i18183.findRef.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// A minimised reproduction of how an initial change to combineEligibles broke Typer#findRef
2+
case class Foo(n: Int)
3+
4+
class Test:
5+
import this.toString
6+
7+
val foo1 = Foo(1)
8+
val foo2 = Foo(2)
9+
10+
def foo(using Foo): Foo =
11+
import this.*
12+
def bar(using Foo): Foo = summon[Foo]
13+
bar(using foo2)
14+
15+
object Test extends Test:
16+
def main(args: Array[String]): Unit =
17+
assert(foo(using foo1) eq foo2)

tests/run/i18183.given.scala

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
case class Foo(n: Int)
2+
3+
class Bar(n: Int):
4+
given foo: Foo = new Foo(n)
5+
6+
class InMethod:
7+
def wild(bar: Bar): Unit =
8+
import bar.*
9+
given foo: Foo = new Foo(2)
10+
assert(foo eq summon[Foo])
11+
12+
def givenWild(bar: Bar): Unit =
13+
import bar.{ given, * }
14+
given foo: Foo = new Foo(2)
15+
assert(foo eq summon[Foo])
16+
17+
def givn(bar: Bar): Unit =
18+
import bar.given
19+
given foo: Foo = new Foo(2)
20+
assert(foo eq summon[Foo])
21+
22+
def givenFoo(bar: Bar): Unit =
23+
import bar.given Foo
24+
given foo: Foo = new Foo(2)
25+
assert(foo eq summon[Foo])
26+
27+
def named(bar: Bar): Unit =
28+
import bar.foo
29+
given foo: Foo = new Foo(2)
30+
assert(foo eq summon[Foo])
31+
32+
def namedGivenWild(bar: Bar, bar2: Bar): Unit =
33+
import bar.foo, bar2.{ given, * }
34+
assert(bar.foo eq summon[Foo])
35+
36+
def givenWildNamed(bar: Bar, bar2: Bar): Unit =
37+
import bar2.{ given, * }, bar.foo
38+
assert(bar.foo eq summon[Foo])
39+
40+
def namedWild(bar: Bar, bar2: Bar): Unit =
41+
import bar.foo, bar2.*
42+
assert(bar.foo eq summon[Foo])
43+
44+
def wildNamed(bar: Bar, bar2: Bar): Unit =
45+
import bar2.*, bar.foo
46+
assert(bar.foo eq summon[Foo])
47+
48+
class InClassWild(bar: Bar):
49+
import bar.*
50+
given foo: Foo = new Foo(2)
51+
assert(foo eq summon[Foo])
52+
53+
class InClassGivenWild(bar: Bar):
54+
import bar.{ given, * }
55+
given foo: Foo = new Foo(2)
56+
assert(foo eq summon[Foo])
57+
58+
class InClassGiven(bar: Bar):
59+
import bar.given
60+
given foo: Foo = new Foo(2)
61+
assert(foo eq summon[Foo])
62+
63+
class InClassGivenFoo(bar: Bar):
64+
import bar.given Foo
65+
given foo: Foo = new Foo(2)
66+
assert(foo eq summon[Foo])
67+
68+
class InClassNamed(bar: Bar):
69+
import bar.foo
70+
given foo: Foo = new Foo(2)
71+
assert(foo eq summon[Foo])
72+
73+
object Test:
74+
def main(args: Array[String]): Unit =
75+
val bar = new Bar(1)
76+
val bar2 = new Bar(2)
77+
78+
new InMethod().wild(bar)
79+
new InMethod().givenWild(bar) // was: error
80+
new InMethod().givn(bar) // was: error
81+
new InMethod().givenFoo(bar) // was: error
82+
new InMethod().named(bar) // was: error
83+
84+
new InMethod().namedWild(bar, bar2)
85+
new InMethod().wildNamed(bar, bar2)
86+
new InMethod().namedGivenWild(bar, bar2) // was: error
87+
new InMethod().givenWildNamed(bar, bar2)
88+
89+
new InClassWild(bar)
90+
new InClassGivenWild(bar) // was: error
91+
new InClassGiven(bar) // was: error
92+
new InClassGivenFoo(bar) // was: error
93+
new InClassNamed(bar) // was: error

tests/run/i18183.mixed.scala

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
case class Foo(n: Int)
2+
3+
class OldBar(n: Int):
4+
implicit val foo: Foo = new Foo(n)
5+
6+
class NewBar(n: Int):
7+
given foo: Foo = new Foo(n)
8+
9+
class OldInMethod:
10+
def wild(bar: OldBar): Unit =
11+
import bar.*
12+
given foo: Foo = new Foo(2)
13+
assert(foo eq summon[Foo])
14+
15+
def named(bar: OldBar): Unit =
16+
import bar.foo
17+
given foo: Foo = new Foo(2)
18+
assert(foo eq summon[Foo])
19+
20+
def namedWild(bar: OldBar, bar2: NewBar): Unit =
21+
import bar.foo, bar2.*
22+
assert(bar.foo eq summon[Foo])
23+
24+
def wildNamed(bar: OldBar, bar2: NewBar): Unit =
25+
import bar2.*, bar.foo
26+
assert(bar.foo eq summon[Foo])
27+
28+
def namedGivenWild(bar: OldBar, bar2: NewBar): Unit =
29+
import bar.foo
30+
import bar2.{ given, * }
31+
assert(bar.foo eq summon[Foo])
32+
33+
def givenWildNamed(bar: OldBar, bar2: NewBar): Unit =
34+
import bar2.{ given, * }, bar.foo
35+
assert(bar.foo eq summon[Foo])
36+
37+
class OldInClassWild(bar: OldBar):
38+
import bar.*
39+
given foo: Foo = new Foo(2)
40+
assert(foo eq summon[Foo])
41+
42+
class OldInClassNamed(bar: OldBar):
43+
import bar.foo
44+
given foo: Foo = new Foo(2)
45+
assert(foo eq summon[Foo])
46+
47+
48+
class NewInMethod:
49+
def givenWild(bar: NewBar): Unit =
50+
import bar.{ given, * }
51+
implicit val foo: Foo = new Foo(2)
52+
assert(foo eq summon[Foo])
53+
54+
def wild(bar: NewBar): Unit =
55+
import bar.*
56+
implicit val foo: Foo = new Foo(2)
57+
assert(foo eq summon[Foo])
58+
59+
def givn(bar: NewBar): Unit =
60+
import bar.given
61+
implicit val foo: Foo = new Foo(2)
62+
assert(foo eq summon[Foo])
63+
64+
def givenFoo(bar: NewBar): Unit =
65+
import bar.given Foo
66+
implicit val foo: Foo = new Foo(2)
67+
assert(foo eq summon[Foo])
68+
69+
def named(bar: NewBar): Unit =
70+
import bar.foo
71+
implicit val foo: Foo = new Foo(2)
72+
assert(foo eq summon[Foo])
73+
74+
def namedWild(bar: NewBar, bar2: OldBar): Unit =
75+
import bar.foo, bar2.*
76+
assert(bar.foo eq summon[Foo])
77+
78+
def wildNamed(bar: NewBar, bar2: OldBar): Unit =
79+
import bar2.*, bar.foo
80+
assert(bar.foo eq summon[Foo])
81+
82+
class NewInClassGivenWild(bar: NewBar):
83+
import bar.{ given, * }
84+
implicit val foo: Foo = new Foo(2)
85+
assert(foo eq summon[Foo])
86+
87+
class NewInClassWild(bar: NewBar):
88+
import bar.*
89+
implicit val foo: Foo = new Foo(2)
90+
assert(foo eq summon[Foo])
91+
92+
class NewInClassGiven(bar: NewBar):
93+
import bar.given
94+
implicit val foo: Foo = new Foo(2)
95+
assert(foo eq summon[Foo])
96+
97+
class NewInClassGivenFoo(bar: NewBar):
98+
import bar.given Foo
99+
implicit val foo: Foo = new Foo(2)
100+
assert(foo eq summon[Foo])
101+
102+
class NewInClassNamed(bar: NewBar):
103+
import bar.foo
104+
implicit val foo: Foo = new Foo(2)
105+
assert(foo eq summon[Foo])
106+
107+
108+
object Test:
109+
def main(args: Array[String]): Unit =
110+
val oldBar = new OldBar(1)
111+
val newBar = new NewBar(1)
112+
val oldBar2 = new OldBar(2)
113+
val newBar2 = new NewBar(2)
114+
115+
116+
new OldInMethod().wild(oldBar) // was: error
117+
new OldInMethod().named(oldBar) // was: error
118+
119+
new OldInMethod().namedWild(oldBar, newBar2)
120+
new OldInMethod().wildNamed(oldBar, newBar2)
121+
new OldInMethod().namedGivenWild(oldBar, newBar2) // was: error
122+
new OldInMethod().givenWildNamed(oldBar, newBar2)
123+
124+
new OldInClassWild(oldBar) // was: error
125+
new OldInClassNamed(oldBar) // was: error
126+
127+
128+
new NewInMethod().wild(newBar)
129+
new NewInMethod().givenWild(newBar) // was: error
130+
new NewInMethod().givn(newBar) // was: error
131+
new NewInMethod().givenFoo(newBar) // was: error
132+
new NewInMethod().named(newBar) // was: error
133+
134+
new NewInMethod().namedWild(newBar, oldBar2) // was: error
135+
new NewInMethod().wildNamed(newBar, oldBar2)
136+
137+
new NewInClassWild(newBar)
138+
new NewInClassGivenWild(newBar) // was: error
139+
new NewInClassGiven(newBar) // was: error
140+
new NewInClassGivenFoo(newBar) // was: error
141+
new NewInClassNamed(newBar) // was: error

tests/run/i18183.scala

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
case class Foo(n: Int)
2+
3+
class Bar(n: Int):
4+
implicit val foo: Foo = new Foo(n)
5+
6+
class InMethod:
7+
def wild(bar: Bar): Unit =
8+
import bar._
9+
implicit val foo: Foo = new Foo(2)
10+
assert(foo eq implicitly[Foo])
11+
12+
def named(bar: Bar): Unit =
13+
import bar.foo
14+
implicit val foo: Foo = new Foo(2)
15+
assert(foo eq implicitly[Foo])
16+
17+
def namedWild(bar: Bar, bar2: Bar): Unit =
18+
import bar.foo
19+
import bar2._
20+
assert(bar.foo eq implicitly[Foo])
21+
22+
def wildNamed(bar: Bar, bar2: Bar): Unit =
23+
import bar2._
24+
import bar.foo
25+
assert(bar.foo eq implicitly[Foo])
26+
27+
class InClassWild(bar: Bar):
28+
import bar._
29+
implicit val foo: Foo = new Foo(2)
30+
assert(foo eq implicitly[Foo])
31+
32+
class InClassNamed(bar: Bar):
33+
import bar.foo
34+
implicit val foo: Foo = new Foo(2)
35+
assert(foo eq implicitly[Foo])
36+
37+
object Test:
38+
def main(args: Array[String]): Unit =
39+
val bar = new Bar(1)
40+
val bar2 = new Bar(2)
41+
42+
new InMethod().wild(bar) // was: error
43+
new InMethod().named(bar) // was: error
44+
45+
new InMethod().namedWild(bar, bar2) // was: error
46+
new InMethod().wildNamed(bar, bar2)
47+
48+
new InClassWild(bar) // was: error
49+
new InClassNamed(bar) // was: error

0 commit comments

Comments
 (0)