Skip to content

Commit c529ac2

Browse files
committed
Fix #21402: Always allow type member extraction for stable scrutinees in match types.
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.
1 parent a672e05 commit c529ac2

File tree

3 files changed

+97
-11
lines changed

3 files changed

+97
-11
lines changed

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

Lines changed: 29 additions & 11 deletions
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

Lines changed: 41 additions & 0 deletions
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+
}
Lines changed: 27 additions & 0 deletions
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)