diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 8fc6307c426c..ee304e9c619f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3681,19 +3681,36 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { stableScrut.member(typeMemberName) match case denot: SingleDenotation if denot.exists => - val info = denot.info match - case alias: AliasingBounds => alias.alias // Extract the alias - case ClassInfo(prefix, cls, _, _, _) => prefix.select(cls) // Re-select the class from the prefix - case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances - val info1 = stableScrut match + val info = stableScrut match case skolem: SkolemType => - dropSkolem(info, skolem).orElse: - info match - case info: TypeBounds => info // Will already trigger a MatchResult.NoInstances - case _ => RealTypeBounds(info, info) // Explicitly trigger a MatchResult.NoInstances - case _ => info - rec(capture, info1, variance = 0, scrutIsWidenedAbstract) + /* If it is a skolem type, we cannot have class selections nor + * abstract type selections. If it is an alias, we try to remove + * any reference to the skolem from the right-hand-side. If that + * succeeds, we take the result, otherwise we fail as not-specific. + */ + def adaptToTriggerNotSpecific(info: Type): Type = info match + case info: TypeBounds => info + case _ => RealTypeBounds(info, info) + + denot.info match + case denotInfo: AliasingBounds => + val alias = denotInfo.alias + dropSkolem(alias, skolem).orElse(adaptToTriggerNotSpecific(alias)) + case ClassInfo(prefix, cls, _, _, _) => + // for clean error messages + adaptToTriggerNotSpecific(prefix.select(cls)) + case denotInfo => + adaptToTriggerNotSpecific(denotInfo) + + case _ => + // The scrutinee type is truly stable. We select the type member directly on it. + stableScrut.select(typeMemberName) + end info + + rec(capture, info, variance = 0, scrutIsWidenedAbstract) + case _ => + // The type member was not found; no match false end rec diff --git a/tests/pos/i21402.scala b/tests/pos/i21402.scala new file mode 100644 index 000000000000..4ddf201ef8b4 --- /dev/null +++ b/tests/pos/i21402.scala @@ -0,0 +1,41 @@ +abstract class AbstractServiceKey: + type Protocol + +abstract class ServiceKey[T] extends AbstractServiceKey: + type Protocol = T + +type Aux[P] = AbstractServiceKey { type Protocol = P } +type Service[K <: Aux[?]] = K match + case Aux[t] => ActorRef[t] +type Subscriber[K <: Aux[?]] = K match + case Aux[t] => ActorRef[ReceptionistMessages.Listing[t]] + +trait ActorRef[-T] + +object ReceptionistMessages: + final case class Listing[T](key: ServiceKey[T]) + +class TypedMultiMap[T <: AnyRef, K[_ <: T]]: + def get(key: T): Set[K[key.type]] = ??? + transparent inline def getInlined(key: T): Set[K[key.type]] = ??? + inline def inserted(key: T, value: K[key.type]): TypedMultiMap[T, K] = ??? + +object LocalReceptionist { + final case class State( + services: TypedMultiMap[AbstractServiceKey, Service], + subscriptions: TypedMultiMap[AbstractServiceKey, Subscriber] + ): + def testInsert(key: AbstractServiceKey)(serviceInstance: ActorRef[key.Protocol]): State = { + val fails = services.inserted(key, serviceInstance) // error + ??? + } + + def testGet[T](key: AbstractServiceKey): Unit = { + val newState: State = ??? + val fails: Set[ActorRef[key.Protocol]] = newState.services.get(key) // error + val works: Set[ActorRef[key.Protocol]] = newState.services.getInlined(key) // workaround + + val fails2: Set[ActorRef[ReceptionistMessages.Listing[key.Protocol]]] = newState.subscriptions.get(key) // error + val works2: Set[ActorRef[ReceptionistMessages.Listing[key.Protocol]]] = newState.subscriptions.getInlined(key) // workaround + } +}