Skip to content

Commit

Permalink
Fix #21402: Always allow type member extraction for stable scrutinees…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
sjrd committed Oct 4, 2024
1 parent a672e05 commit dc95aea
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 11 deletions.
39 changes: 28 additions & 11 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
41 changes: 41 additions & 0 deletions tests/pos/i21402.scala
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit dc95aea

Please sign in to comment.