Skip to content

Commit 098b376

Browse files
committed
Handle type member extractors as specced match types.
1 parent 343235b commit 098b376

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
@@ -3354,6 +3354,29 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33543354
rec(argPattern, ConstantType(Constant(scrutValue - 1)), variance, scrutIsWidenedAbstract)
33553355
case _ =>
33563356
false
3357+
3358+
case MatchTypeCasePattern.TypeMemberExtractor(typeMemberName, capture) =>
3359+
val stableScrut: SingletonType = scrut match
3360+
case scrut: SingletonType => scrut
3361+
case _ => SkolemType(scrut)
3362+
stableScrut.member(typeMemberName) match
3363+
case denot: SingleDenotation if denot.exists =>
3364+
val info = denot.info match
3365+
case TypeAlias(alias) => alias
3366+
case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances
3367+
if info.isInstanceOf[ClassInfo] then
3368+
/* The member is not an alias (we'll get Stuck instead of NoInstances,
3369+
* which is not ideal, but we cannot make a RealTypeBounds of ClassInfo).
3370+
*/
3371+
false
3372+
else
3373+
val infoRefersToSkolem = stableScrut.isInstanceOf[SkolemType] && stableScrut.occursIn(info)
3374+
val info1 =
3375+
if infoRefersToSkolem && !info.isInstanceOf[TypeBounds] then RealTypeBounds(info, info) // to trigger a MatchResult.NoInstances
3376+
else info
3377+
rec(capture, info1, variance = 0, scrutIsWidenedAbstract)
3378+
case _ =>
3379+
false
33573380
end rec
33583381

33593382
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
@@ -5097,6 +5097,7 @@ object Types {
50975097
case BaseTypeTest(classType: TypeRef, argPatterns: List[MatchTypeCasePattern], needsConcreteScrut: Boolean)
50985098
case CompileTimeS(argPattern: MatchTypeCasePattern)
50995099
case AbstractTypeConstructor(tycon: Type, argPatterns: List[MatchTypeCasePattern])
5100+
case TypeMemberExtractor(typeMemberName: TypeName, capture: Capture)
51005101

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

51975210
case pat @ AppliedType(tycon: TypeParamRef, _) if variance == 1 =>
51985211
recAbstractTypeConstructor(pat)
51995212

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