Skip to content

Commit d6e0313

Browse files
committed
Handle type member extractors as specced match types.
1 parent 5f3fbf5 commit d6e0313

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
@@ -3346,6 +3346,36 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33463346
rec(argPattern, ConstantType(Constant(scrutValue - 1)), variance, scrutIsWidenedAbstract)
33473347
case _ =>
33483348
false
3349+
3350+
case MatchTypeCasePattern.TypeMemberExtractor(typeMemberName, capture) =>
3351+
val stableScrut: SingletonType = scrut match
3352+
case scrut: SingletonType => scrut
3353+
case _ => SkolemType(scrut)
3354+
stableScrut.member(typeMemberName) match
3355+
case denot: SingleDenotation if denot.exists =>
3356+
val info = denot.info match
3357+
case TypeAlias(alias) => alias
3358+
case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances
3359+
if info.isInstanceOf[ClassInfo] then
3360+
/* The member is not an alias (we'll get Stuck instead of NoInstances,
3361+
* which is not ideal, but we cannot make a RealTypeBounds of ClassInfo).
3362+
*/
3363+
false
3364+
else
3365+
val infoRefersToSkolem = stableScrut match
3366+
case stableScrut: SkolemType =>
3367+
new TypeAccumulator[Boolean] {
3368+
def apply(prev: Boolean, tp: Type): Boolean =
3369+
prev || (tp eq stableScrut) || foldOver(prev, tp)
3370+
}.apply(false, info)
3371+
case _ =>
3372+
false
3373+
val info1 =
3374+
if infoRefersToSkolem && !info.isInstanceOf[TypeBounds] then RealTypeBounds(info, info) // to trigger a MatchResult.NoInstances
3375+
else info
3376+
rec(capture, info1, variance = 0, scrutIsWidenedAbstract)
3377+
case _ =>
3378+
false
33493379
end rec
33503380

33513381
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
@@ -5066,6 +5066,7 @@ object Types {
50665066
case BaseTypeTest(classType: TypeRef, argPatterns: List[MatchTypeCasePattern], needsConcreteScrut: Boolean)
50675067
case CompileTimeS(argPattern: MatchTypeCasePattern)
50685068
case AbstractTypeConstructor(tycon: Type, argPatterns: List[MatchTypeCasePattern])
5069+
case TypeMemberExtractor(typeMemberName: TypeName, capture: Capture)
50695070

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

51665179
case pat @ AppliedType(tycon: TypeParamRef, _) if variance == 1 =>
51675180
recAbstractTypeConstructor(pat)
51685181

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