Skip to content

Commit 97725d7

Browse files
committed
Handle type member extractors as specced match types.
1 parent aa8d348 commit 97725d7

File tree

3 files changed

+88
-2
lines changed

3 files changed

+88
-2
lines changed

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3362,6 +3362,29 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33623362
rec(argPattern, ConstantType(Constant(scrutValue - 1)), variance, scrutIsWidenedAbstract)
33633363
case _ =>
33643364
false
3365+
3366+
case MatchTypeCasePattern.TypeMemberExtractor(typeMemberName, capture) =>
3367+
val stableScrut: SingletonType = scrut match
3368+
case scrut: SingletonType => scrut
3369+
case _ => SkolemType(scrut)
3370+
stableScrut.member(typeMemberName) match
3371+
case denot: SingleDenotation if denot.exists =>
3372+
val info = denot.info match
3373+
case TypeAlias(alias) => alias
3374+
case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances
3375+
if info.isInstanceOf[ClassInfo] then
3376+
/* The member is not an alias (we'll get Stuck instead of NoInstances,
3377+
* which is not ideal, but we cannot make a RealTypeBounds of ClassInfo).
3378+
*/
3379+
false
3380+
else
3381+
val infoRefersToSkolem = stableScrut.isInstanceOf[SkolemType] && stableScrut.occursIn(info)
3382+
val info1 =
3383+
if infoRefersToSkolem && !info.isInstanceOf[TypeBounds] then RealTypeBounds(info, info) // to trigger a MatchResult.NoInstances
3384+
else info
3385+
rec(capture, info1, variance = 0, scrutIsWidenedAbstract)
3386+
case _ =>
3387+
false
33653388
end rec
33663389

33673390
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
@@ -5094,6 +5094,7 @@ object Types extends TypeUtils {
50945094
case BaseTypeTest(classType: TypeRef, argPatterns: List[MatchTypeCasePattern], needsConcreteScrut: Boolean)
50955095
case CompileTimeS(argPattern: MatchTypeCasePattern)
50965096
case AbstractTypeConstructor(tycon: Type, argPatterns: List[MatchTypeCasePattern])
5097+
case TypeMemberExtractor(typeMemberName: TypeName, capture: Capture)
50975098

50985099
def isTypeTest: Boolean =
50995100
this.isInstanceOf[TypeTest]
@@ -5187,12 +5188,45 @@ object Types extends TypeUtils {
51875188
MatchTypeCasePattern.CompileTimeS(argPattern)
51885189
else
51895190
tycon.info match
5190-
case _: RealTypeBounds => recAbstractTypeConstructor(pat)
5191-
case _ => null
5191+
case _: RealTypeBounds =>
5192+
recAbstractTypeConstructor(pat)
5193+
case TypeAlias(tl @ HKTypeLambda(onlyParam :: Nil, resType: RefinedType)) =>
5194+
/* Unlike for eta-expanded classes, the typer does not automatically
5195+
* dealias poly type aliases to refined types. So we have to give them
5196+
* a chance here.
5197+
* We are quite specific about the shape of type aliases that we are willing
5198+
* to dealias this way, because we must not dealias arbitrary type constructors
5199+
* that could refine the bounds of the captures; those would amount of
5200+
* type-test + capture combos, which are out of the specced match types.
5201+
*/
5202+
rec(pat.superType, variance)
5203+
case _ =>
5204+
null
51925205

51935206
case pat @ AppliedType(tycon: TypeParamRef, _) if variance == 1 =>
51945207
recAbstractTypeConstructor(pat)
51955208

5209+
case pat @ RefinedType(parent, refinedName: TypeName, TypeAlias(alias @ TypeParamRef(binder, num)))
5210+
if variance == 1 && (binder eq caseLambda) =>
5211+
parent.member(refinedName) match
5212+
case refinedMember: SingleDenotation if refinedMember.exists =>
5213+
// Check that the bounds of the capture contain the bounds of the inherited member
5214+
val refinedMemberBounds = refinedMember.info
5215+
val captureBounds = caseLambda.paramInfos(num)
5216+
if captureBounds.contains(refinedMemberBounds) then
5217+
/* In this case, we know that any member we eventually find during reduction
5218+
* will have bounds that fit in the bounds of the capture. Therefore, no
5219+
* type-test + capture combo is necessary, and we can apply the specced match types.
5220+
*/
5221+
val capture = rec(alias, variance = 0).asInstanceOf[MatchTypeCasePattern.Capture]
5222+
MatchTypeCasePattern.TypeMemberExtractor(refinedName, capture)
5223+
else
5224+
// Otherwise, a type-test + capture combo might be necessary, and we are out of spec
5225+
null
5226+
case _ =>
5227+
// If the member does not refine a member of the `parent`, we are out of spec
5228+
null
5229+
51965230
case _ =>
51975231
MatchTypeCasePattern.TypeTest(pat)
51985232
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)