Skip to content

Commit 048951c

Browse files
committed
Handle type member extractors as specced match types.
1 parent 9c19ada commit 048951c

File tree

7 files changed

+115
-13
lines changed

7 files changed

+115
-13
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
@@ -5063,6 +5063,7 @@ object Types {
50635063
case BaseTypeTest(classType: TypeRef, argPatterns: List[MatchTypeCasePattern], needsConcreteScrut: Boolean)
50645064
case CompileTimeS(argPattern: MatchTypeCasePattern)
50655065
case AbstractTypeConstructor(tycon: Type, argPatterns: List[MatchTypeCasePattern])
5066+
case TypeMemberExtractor(typeMemberName: TypeName, capture: Capture)
50665067

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

51635176
case pat @ AppliedType(tycon: TypeParamRef, _) if variance == 1 =>
51645177
recAbstractTypeConstructor(pat)
51655178

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

tests/neg/legacy-match-types.check

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@
2222
| Illegal match type because it contains the legacy, unspecifed case
2323
| case IsSeq[t] => t
2424
23 | case IsSeq[t] => t
25-
-- [E189] Type Error: tests/neg/legacy-match-types.scala:33:34 ---------------------------------------------------------
26-
33 |type TypeMemberExtractorMT[X] = X match // error
25+
-- [E189] Type Error: tests/neg/legacy-match-types.scala:29:34 ---------------------------------------------------------
26+
29 |type TypeMemberExtractorMT[X] = X match // error
2727
| ^
2828
| Illegal match type because it contains the legacy, unspecifed case
2929
| case TypeMemberAux[t] => t
30-
34 | case TypeMemberAux[t] => t
30+
30 | case TypeMemberAux[t] => t
31+
-- [E189] Type Error: tests/neg/legacy-match-types.scala:40:35 ---------------------------------------------------------
32+
40 |type TypeMemberExtractorMT2[X] = X match // error
33+
| ^
34+
| Illegal match type because it contains the legacy, unspecifed case
35+
| case TypeMemberAux2[t] => t
36+
41 | case TypeMemberAux2[t] => t

tests/neg/legacy-match-types.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,20 @@ type IsSeq[X <: Seq[Any]] = X
2222
type TypeAliasWithBoundMT[X] = X match // error
2323
case IsSeq[t] => t
2424

25-
// Poly type alias with a type member refinement to extract the type member
25+
// Poly type alias with an unknown type member refinement
26+
27+
type TypeMemberAux[X] = { type TypeMember = X }
28+
29+
type TypeMemberExtractorMT[X] = X match // error
30+
case TypeMemberAux[t] => t
31+
32+
// Poly type alias with a refined member of stronger bounds than in the parent
2633

2734
class Base {
2835
type TypeMember
2936
}
3037

31-
type TypeMemberAux[X] = Base { type TypeMember = X }
38+
type TypeMemberAux2[X <: Seq[Any]] = Base { type TypeMember = X }
3239

33-
type TypeMemberExtractorMT[X] = X match // error
34-
case TypeMemberAux[t] => t
40+
type TypeMemberExtractorMT2[X] = X match // error
41+
case TypeMemberAux2[t] => t

tests/pos/i16408.min1.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// scalac: -Yno-legacy-match-types:false
2-
31
object Helpers:
42
type NodeFun[R] = Matchable // compiles without [R] parameter
53

tests/pos/i16408.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// scalac: -Yno-legacy-match-types:false
2-
31
import scala.util.Try
42

53
trait RDF:

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)