Skip to content

Commit 2023c5d

Browse files
authored
Fix #21402: Always allow type member extraction for stable scrutinees in match types. (#21700)
Previously, through the various code paths, we basically allowed type member extraction for stable scrutinees if the type member was an alias or a class member. In the alias case, we took the alias, whereas in the class case, we recreated a selection on the stable scrutinee. We did not allow that on abstract type members. We now uniformly do it for all kinds of type members. If the scrutinee is a (non-skolem) stable type, we do not even look at the info of the type member. We directly create a selection to it, which corresponds to what we did before for class members. We only try to dealias type members if the scrutinee type is not a stable type.
2 parents 6c7619a + c529ac2 commit 2023c5d

File tree

3 files changed

+97
-11
lines changed

3 files changed

+97
-11
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+29-11
Original file line numberDiff line numberDiff line change
@@ -3681,19 +3681,37 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
36813681

36823682
stableScrut.member(typeMemberName) match
36833683
case denot: SingleDenotation if denot.exists =>
3684-
val info = denot.info match
3685-
case alias: AliasingBounds => alias.alias // Extract the alias
3686-
case ClassInfo(prefix, cls, _, _, _) => prefix.select(cls) // Re-select the class from the prefix
3687-
case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances
3688-
val info1 = stableScrut match
3684+
val info = stableScrut match
36893685
case skolem: SkolemType =>
3690-
dropSkolem(info, skolem).orElse:
3691-
info match
3692-
case info: TypeBounds => info // Will already trigger a MatchResult.NoInstances
3693-
case _ => RealTypeBounds(info, info) // Explicitly trigger a MatchResult.NoInstances
3694-
case _ => info
3695-
rec(capture, info1, variance = 0, scrutIsWidenedAbstract)
3686+
/* If it is a skolem type, we cannot have class selections nor
3687+
* abstract type selections. If it is an alias, we try to remove
3688+
* any reference to the skolem from the right-hand-side. If that
3689+
* succeeds, we take the result, otherwise we fail as not-specific.
3690+
*/
3691+
3692+
def adaptToTriggerNotSpecific(info: Type): Type = info match
3693+
case info: TypeBounds => info
3694+
case _ => RealTypeBounds(info, info)
3695+
3696+
denot.info match
3697+
case denotInfo: AliasingBounds =>
3698+
val alias = denotInfo.alias
3699+
dropSkolem(alias, skolem).orElse(adaptToTriggerNotSpecific(alias))
3700+
case ClassInfo(prefix, cls, _, _, _) =>
3701+
// for clean error messages
3702+
adaptToTriggerNotSpecific(prefix.select(cls))
3703+
case denotInfo =>
3704+
adaptToTriggerNotSpecific(denotInfo)
3705+
3706+
case _ =>
3707+
// The scrutinee type is truly stable. We select the type member directly on it.
3708+
stableScrut.select(typeMemberName)
3709+
end info
3710+
3711+
rec(capture, info, variance = 0, scrutIsWidenedAbstract)
3712+
36963713
case _ =>
3714+
// The type member was not found; no match
36973715
false
36983716
end rec
36993717

tests/pos/i21402.scala

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
abstract class AbstractServiceKey:
2+
type Protocol
3+
4+
abstract class ServiceKey[T] extends AbstractServiceKey:
5+
type Protocol = T
6+
7+
type Aux[P] = AbstractServiceKey { type Protocol = P }
8+
type Service[K <: Aux[?]] = K match
9+
case Aux[t] => ActorRef[t]
10+
type Subscriber[K <: Aux[?]] = K match
11+
case Aux[t] => ActorRef[ReceptionistMessages.Listing[t]]
12+
13+
trait ActorRef[-T]
14+
15+
object ReceptionistMessages:
16+
final case class Listing[T](key: ServiceKey[T])
17+
18+
class TypedMultiMap[T <: AnyRef, K[_ <: T]]:
19+
def get(key: T): Set[K[key.type]] = ???
20+
transparent inline def getInlined(key: T): Set[K[key.type]] = ???
21+
inline def inserted(key: T, value: K[key.type]): TypedMultiMap[T, K] = ???
22+
23+
object LocalReceptionist {
24+
final case class State(
25+
services: TypedMultiMap[AbstractServiceKey, Service],
26+
subscriptions: TypedMultiMap[AbstractServiceKey, Subscriber]
27+
):
28+
def testInsert(key: AbstractServiceKey)(serviceInstance: ActorRef[key.Protocol]): State = {
29+
val fails = services.inserted(key, serviceInstance) // error
30+
???
31+
}
32+
33+
def testGet[T](key: AbstractServiceKey): Unit = {
34+
val newState: State = ???
35+
val fails: Set[ActorRef[key.Protocol]] = newState.services.get(key) // error
36+
val works: Set[ActorRef[key.Protocol]] = newState.services.getInlined(key) // workaround
37+
38+
val fails2: Set[ActorRef[ReceptionistMessages.Listing[key.Protocol]]] = newState.subscriptions.get(key) // error
39+
val works2: Set[ActorRef[ReceptionistMessages.Listing[key.Protocol]]] = newState.subscriptions.getInlined(key) // workaround
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Test that match types can extract path-dependent abstract types out of singleton types
2+
3+
trait Base:
4+
type Value
5+
6+
def getValue(): Value
7+
def setValue(v: Value): Unit
8+
end Base
9+
10+
object Extractor:
11+
type Helper[X] = Base { type Value = X }
12+
13+
type Extract[B <: Base] = B match
14+
case Helper[x] => x
15+
end Extractor
16+
17+
object Test:
18+
import Extractor.Extract
19+
20+
/* As is, this is a bit silly, since we could use `b.Value` instead. However,
21+
* in larger examples with more indirections, it is not always possible to
22+
* directly use the path-dependent version. See i21402 for a real-world use
23+
* case.
24+
*/
25+
def foo(b: Base): Extract[b.type] = b.getValue()
26+
def bar(b: Base, v: Extract[b.type]): Unit = b.setValue(v)
27+
end Test

0 commit comments

Comments
 (0)