Skip to content

Commit

Permalink
Normalize types before collecting parts determining implicit scope
Browse files Browse the repository at this point in the history
This is necessary to ensure the implicit scope is consistent when involving match types, since they may or may not have been reduced before implicit search.
We can for example get different results when loading from tasty than when in the same run.

Fixes #20071
  • Loading branch information
EugeneFlesselle committed Apr 3, 2024
1 parent 9a5b9b4 commit 90c3fbd
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 3 deletions.
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ trait ImplicitRunInfo:
else if implicitScopeCache.contains(t) then parts += t
else
partSeen += t
t.dealias match
t.dealias.normalized match
case t: TypeRef =>
if isAnchor(t.symbol) then
parts += t
Expand All @@ -663,7 +663,6 @@ trait ImplicitRunInfo:
traverseChildren(t)
case t =>
traverseChildren(t)
traverse(t.normalized)
catch case ex: Throwable => handleRecursive("collectParts of", t.show, ex)

def apply(tp: Type): collection.Set[Type] =
Expand Down Expand Up @@ -775,6 +774,7 @@ trait ImplicitRunInfo:
* if `T` is of the form `(P#x).type`, the anchors of `P`.
* - If `T` is the this-type of a static object, the anchors of a term reference to that object.
* - If `T` is some other this-type `P.this.type`, the anchors of `P`.
* - If `T` is match type or an applied match alias, the anchors of the normalization of `T`.
* - If `T` is some other type, the union of the anchors of each constituent type of `T`.
*
* The _implicit scope_ of a type `tp` is the smallest set S of term references (i.e. TermRefs)
Expand All @@ -787,7 +787,7 @@ trait ImplicitRunInfo:
* - If `T` is a reference to an opaque type alias named `A`, S includes
* a reference to an object `A` defined in the same scope as the type, if it exists,
* as well as the implicit scope of `T`'s underlying type or bounds.
* - If `T` is a reference to an an abstract type or match type alias named `A`,
* - If `T` is a reference to an an abstract type or unreducible match type alias named `A`,
* S includes a reference to an object `A` defined in the same scope as the type,
* if it exists, as well as the implicit scopes of `T`'s lower and upper bound,
* if present.
Expand Down
28 changes: 28 additions & 0 deletions tests/neg/i20071.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

trait Scope
object Scope:
given i: Int = ???

type ReferencesScope[S] >: Int <: Int

type ScopeToInt[Why] = Why match
case Scope => Int

def foo[T](using d: ReferencesScope[T]): Any = ???

def bar[T](using d: ScopeToInt[T]): Any = ???

def test: Unit =
foo[Scope] // ok
bar[Scope] // error

import Scope.i
bar[Scope] // ok

/*
Before the changes:
`ScopeToInt[Scope]` may or may not be reduced before implicit search,
thereby impacting the scope considered for the search. `Scope.i` is included
iff `Scope` still appears in the type, which is the case only before reduction.
In contrast, `ReferencesScope[Scope]` is ok since it will never lose the anchor.
*/
4 changes: 4 additions & 0 deletions tests/pos/i15183/test_2.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
// Fails in each cases below
import Decoder.{derived as _, given}
// NOTE Decoder.derived is already in the implicit scope
// but the others require an import as they depend on match type reduction

enum Env derives Decoder:
case Local,Sit,Prod

Expand Down

0 comments on commit 90c3fbd

Please sign in to comment.