Skip to content

Commit 648388e

Browse files
committed
Handle type member extractors as specced match types.
1 parent f61acee commit 648388e

File tree

3 files changed

+95
-2
lines changed

3 files changed

+95
-2
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3350,6 +3350,36 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33503350
rec(argPattern, ConstantType(Constant(scrutValue - 1)), variance, scrutIsWidenedAbstract)
33513351
case _ =>
33523352
false
3353+
3354+
case MatchTypeCasePattern.TypeMemberExtractor(typeMemberName, capture) =>
3355+
val stableScrut: SingletonType = scrut match
3356+
case scrut: SingletonType => scrut
3357+
case _ => SkolemType(scrut)
3358+
stableScrut.member(typeMemberName) match
3359+
case denot: SingleDenotation if denot.exists =>
3360+
val info = denot.info match
3361+
case TypeAlias(alias) => alias
3362+
case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances
3363+
if info.isInstanceOf[ClassInfo] then
3364+
/* The member is not an alias (we'll get Stuck instead of NoInstances,
3365+
* which is not ideal, but we cannot make a RealTypeBounds of ClassInfo).
3366+
*/
3367+
false
3368+
else
3369+
val infoRefersToSkolem = stableScrut match
3370+
case stableScrut: SkolemType =>
3371+
new TypeAccumulator[Boolean] {
3372+
def apply(prev: Boolean, tp: Type): Boolean =
3373+
prev || (tp eq stableScrut) || foldOver(prev, tp)
3374+
}.apply(false, info)
3375+
case _ =>
3376+
false
3377+
val info1 =
3378+
if infoRefersToSkolem && !info.isInstanceOf[TypeBounds] then RealTypeBounds(info, info) // to trigger a MatchResult.NoInstances
3379+
else info
3380+
rec(capture, info1, variance = 0, scrutIsWidenedAbstract)
3381+
case _ =>
3382+
false
33533383
end rec
33543384

33553385
def matchArgs(argPatterns: List[MatchTypeCasePattern], args: List[Type], tparams: List[TypeParamInfo], scrutIsWidenedAbstract: Boolean): Boolean =

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5069,6 +5069,7 @@ object Types {
50695069
case BaseTypeTest(classType: TypeRef, argPatterns: List[MatchTypeCasePattern], needsConcreteScrut: Boolean)
50705070
case CompileTimeS(argPattern: MatchTypeCasePattern)
50715071
case AbstractTypeConstructor(tycon: Type, argPatterns: List[MatchTypeCasePattern])
5072+
case TypeMemberExtractor(typeMemberName: TypeName, capture: Capture)
50725073

50735074
def isTypeTest: Boolean =
50745075
this.isInstanceOf[TypeTest]
@@ -5163,12 +5164,45 @@ object Types {
51635164
MatchTypeCasePattern.CompileTimeS(argPattern)
51645165
else
51655166
tycon.info match
5166-
case _: RealTypeBounds => recAbstractTypeConstructor(pat)
5167-
case _ => null
5167+
case _: RealTypeBounds =>
5168+
recAbstractTypeConstructor(pat)
5169+
case TypeAlias(tl @ HKTypeLambda(onlyParam :: Nil, resType: RefinedType)) =>
5170+
/* Unlike for eta-expanded classes, the typer does not automatically
5171+
* dealias poly type aliases to refined types. So we have to give them
5172+
* a chance here.
5173+
* We are quite specific about the shape of type aliases that we are willing
5174+
* to dealias this way, because we must not dealias arbitrary type constructors
5175+
* that could refine the bounds of the captures; those would amount of
5176+
* type-test + capture combos, which are out of the specced match types.
5177+
*/
5178+
rec(pat.superType, variance)
5179+
case _ =>
5180+
null
51685181

51695182
case pat @ AppliedType(tycon: TypeParamRef, _) if variance == 1 =>
51705183
recAbstractTypeConstructor(pat)
51715184

5185+
case pat @ RefinedType(parent, refinedName: TypeName, TypeAlias(alias @ TypeParamRef(binder, num)))
5186+
if variance == 1 && (binder eq caseLambda) =>
5187+
parent.member(refinedName) match
5188+
case refinedMember: SingleDenotation if refinedMember.exists =>
5189+
// Check that the bounds of the capture contain the bounds of the inherited member
5190+
val refinedMemberBounds = refinedMember.info
5191+
val captureBounds = caseLambda.paramInfos(num)
5192+
if captureBounds.contains(refinedMemberBounds) then
5193+
/* In this case, we know that any member we eventually find during reduction
5194+
* will have bounds that fit in the bounds of the capture. Therefore, no
5195+
* type-test + capture combo is necessary, and we can apply the specced match types.
5196+
*/
5197+
val capture = rec(alias, variance = 0).asInstanceOf[MatchTypeCasePattern.Capture]
5198+
MatchTypeCasePattern.TypeMemberExtractor(refinedName, capture)
5199+
else
5200+
// Otherwise, a type-test + capture combo might be necessary, and we are out of spec
5201+
null
5202+
case _ =>
5203+
// If the member does not refine a member of the `parent`, we are out of spec
5204+
null
5205+
51725206
case _ =>
51735207
MatchTypeCasePattern.TypeTest(pat)
51745208
end rec

tests/pos/i17395-spec.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
trait TC[T]
2+
3+
object TC {
4+
def optionTCForPart[T](implicit tc: TC[ExtractPart[T]]): TC[Option[ExtractPart[T]]] = new TC[Option[ExtractPart[T]]] {}
5+
}
6+
7+
trait ThingWithPart {
8+
type Part
9+
}
10+
11+
type ExtractPart[T] = T match {
12+
case PartField[t] => t
13+
}
14+
type PartField[T] = ThingWithPart { type Part = T }
15+
16+
class ValuePartHolder extends ThingWithPart {
17+
type Part = Value
18+
}
19+
20+
class Value
21+
object Value {
22+
implicit val tcValue: TC[Value] = new {}
23+
}
24+
25+
@main def main(): Unit = {
26+
// import Value.tcValue // explicit import works around the issue, but shouldn't be necessary
27+
val tc = TC.optionTCForPart[ValuePartHolder]
28+
println(tc)
29+
}

0 commit comments

Comments
 (0)