Skip to content

Commit

Permalink
Handle type member extractors as specced match types.
Browse files Browse the repository at this point in the history
  • Loading branch information
sjrd committed Aug 10, 2023
1 parent 722d4cb commit 363b4c2
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 13 deletions.
30 changes: 30 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3343,6 +3343,36 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
rec(argPattern, ConstantType(Constant(scrutValue - 1)), variance, scrutIsWidenedAbstract)
case _ =>
false

case MatchTypeCasePattern.TypeMemberExtractor(typeMemberName, capture) =>
val stableScrut: SingletonType = scrut match
case scrut: SingletonType => scrut
case _ => SkolemType(scrut)
stableScrut.member(typeMemberName) match
case denot: SingleDenotation if denot.exists =>
val info = denot.info match
case TypeAlias(alias) => alias
case info => info // Notably, RealTypeBounds, which will eventually give a MatchResult.NoInstances
if info.isInstanceOf[ClassInfo] then
/* The member is not an alias (we'll get Stuck instead of NoInstances,
* which is not ideal, but we cannot make a RealTypeBounds of ClassInfo).
*/
false
else
val infoRefersToSkolem = stableScrut match
case stableScrut: SkolemType =>
new TypeAccumulator[Boolean] {
def apply(prev: Boolean, tp: Type): Boolean =
prev || (tp eq stableScrut) || foldOver(prev, tp)
}.apply(false, info)
case _ =>
false
val info1 =
if infoRefersToSkolem && !info.isInstanceOf[TypeBounds] then RealTypeBounds(info, info) // to trigger a MatchResult.NoInstances
else info
rec(capture, info1, variance = 0, scrutIsWidenedAbstract)
case _ =>
false
end rec

def matchArgs(argPatterns: List[MatchTypeCasePattern], args: List[Type], tparams: List[TypeParamInfo], scrutIsWidenedAbstract: Boolean): Boolean =
Expand Down
38 changes: 36 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5063,6 +5063,7 @@ object Types {
case BaseTypeTest(classType: TypeRef, argPatterns: List[MatchTypeCasePattern], needsConcreteScrut: Boolean)
case CompileTimeS(argPattern: MatchTypeCasePattern)
case AbstractTypeConstructor(tycon: Type, argPatterns: List[MatchTypeCasePattern])
case TypeMemberExtractor(typeMemberName: TypeName, capture: Capture)

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

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

case pat @ RefinedType(parent, refinedName: TypeName, TypeAlias(alias @ TypeParamRef(binder, num)))
if variance == 1 && (binder eq caseLambda) =>
parent.member(refinedName) match
case refinedMember: SingleDenotation if refinedMember.exists =>
// Check that the bounds of the capture contain the bounds of the inherited member
val refinedMemberBounds = refinedMember.info
val captureBounds = caseLambda.paramInfos(num)
if captureBounds.contains(refinedMemberBounds) then
/* In this case, we know that any member we eventually find during reduction
* will have bounds that fit in the bounds of the capture. Therefore, no
* type-test + capture combo is necessary, and we can apply the specced match types.
*/
val capture = rec(alias, variance = 0).asInstanceOf[MatchTypeCasePattern.Capture]
MatchTypeCasePattern.TypeMemberExtractor(refinedName, capture)
else
// Otherwise, a type-test + capture combo might be necessary, and we are out of spec
null
case _ =>
// If the member does not refine a member of the `parent`, we are out of spec
null

case _ =>
MatchTypeCasePattern.TypeTest(pat)
end rec
Expand Down
12 changes: 9 additions & 3 deletions tests/neg/legacy-match-types.check
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@
| Illegal match type because it contains the legacy, unspecifed case
| case IsSeq[t] => t
23 | case IsSeq[t] => t
-- [E189] Type Error: tests/neg/legacy-match-types.scala:33:34 ---------------------------------------------------------
33 |type TypeMemberExtractorMT[X] = X match // error
-- [E189] Type Error: tests/neg/legacy-match-types.scala:29:34 ---------------------------------------------------------
29 |type TypeMemberExtractorMT[X] = X match // error
| ^
| Illegal match type because it contains the legacy, unspecifed case
| case TypeMemberAux[t] => t
34 | case TypeMemberAux[t] => t
30 | case TypeMemberAux[t] => t
-- [E189] Type Error: tests/neg/legacy-match-types.scala:40:35 ---------------------------------------------------------
40 |type TypeMemberExtractorMT2[X] = X match // error
| ^
| Illegal match type because it contains the legacy, unspecifed case
| case TypeMemberAux2[t] => t
41 | case TypeMemberAux2[t] => t
15 changes: 11 additions & 4 deletions tests/neg/legacy-match-types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,20 @@ type IsSeq[X <: Seq[Any]] = X
type TypeAliasWithBoundMT[X] = X match // error
case IsSeq[t] => t

// Poly type alias with a type member refinement to extract the type member
// Poly type alias with an unknown type member refinement

type TypeMemberAux[X] = { type TypeMember = X }

type TypeMemberExtractorMT[X] = X match // error
case TypeMemberAux[t] => t

// Poly type alias with a refined member of stronger bounds than in the parent

class Base {
type TypeMember
}

type TypeMemberAux[X] = Base { type TypeMember = X }
type TypeMemberAux2[X <: Seq[Any]] = Base { type TypeMember = X }

type TypeMemberExtractorMT[X] = X match // error
case TypeMemberAux[t] => t
type TypeMemberExtractorMT2[X] = X match // error
case TypeMemberAux2[t] => t
2 changes: 0 additions & 2 deletions tests/pos/i16408.min1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// scalac: -Yno-legacy-match-types:false

object Helpers:
type NodeFun[R] = Matchable // compiles without [R] parameter

Expand Down
2 changes: 0 additions & 2 deletions tests/pos/i16408.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// scalac: -Yno-legacy-match-types:false

import scala.util.Try

trait RDF:
Expand Down
29 changes: 29 additions & 0 deletions tests/pos/i17395-spec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
trait TC[T]

object TC {
def optionTCForPart[T](implicit tc: TC[ExtractPart[T]]): TC[Option[ExtractPart[T]]] = new TC[Option[ExtractPart[T]]] {}
}

trait ThingWithPart {
type Part
}

type ExtractPart[T] = T match {
case PartField[t] => t
}
type PartField[T] = ThingWithPart { type Part = T }

class ValuePartHolder extends ThingWithPart {
type Part = Value
}

class Value
object Value {
implicit val tcValue: TC[Value] = new {}
}

@main def main(): Unit = {
// import Value.tcValue // explicit import works around the issue, but shouldn't be necessary
val tc = TC.optionTCForPart[ValuePartHolder]
println(tc)
}

0 comments on commit 363b4c2

Please sign in to comment.