Skip to content

Commit

Permalink
Merge pull request #9473 from dwijnand/patmat-break-up-enumerateSubtypes
Browse files Browse the repository at this point in the history
patmat: Break up enumerateSubtypes
  • Loading branch information
dwijnand committed Feb 3, 2021
2 parents 039aec1 + 94e00fb commit 710de52
Showing 1 changed file with 64 additions and 92 deletions.
156 changes: 64 additions & 92 deletions src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
Expand Up @@ -81,94 +81,71 @@ trait TreeAndTypeAnalysis extends Debugging {
trait CheckableTreeAndTypeAnalysis {
val typer: Typer

// TODO: domain of other feasibly enumerable built-in types (char?)
def enumerateSubtypes(tp: Type, grouped: Boolean): List[List[Type]] =
tp.typeSymbol match {
// TODO case _ if tp.isTupleType => // recurse into component types?
case UnitClass if !grouped =>
List(List(UnitTpe))
case BooleanClass if !grouped =>
List(ConstantTrue :: ConstantFalse :: Nil)
// TODO case _ if tp.isTupleType => // recurse into component types
case modSym: ModuleClassSymbol if !grouped =>
List(List(tp))
case sym: RefinementClassSymbol =>
val parentSubtypes = tp.parents.flatMap(parent => enumerateSubtypes(parent, grouped))
if (parentSubtypes exists (_.nonEmpty)) {
// If any of the parents is enumerable, then the refinement type is enumerable.
// We must only include subtypes of the parents that conform to `tp`.
// See neg/virtpatmat_exhaust_compound.scala for an example.
parentSubtypes map (_.filter(_ <:< tp))
}
else Nil
// make sure it's not a primitive, else (5: Byte) match { case 5 => ... } sees no Byte
case sym if sym.isSealed =>

val tpApprox = analyzer.approximateAbstracts(tp)
val pre = tpApprox.prefix

def filterChildren(children: List[Symbol]): List[Type] = {
children flatMap { sym =>
// have to filter out children which cannot match: see ticket #3683 for an example
// compare to the fully known type `tp` (modulo abstract types),
// so that we can rule out stuff like: sealed trait X[T]; class XInt extends X[Int] --> XInt not valid when enumerating X[String]
// however, must approximate abstract types in

val memberType = nestedMemberType(sym, pre, tpApprox.typeSymbol.owner)
val subTp = appliedType(memberType, WildcardType.fillList(sym.typeParams.length))
val subTpApprox = analyzer.approximateAbstracts(subTp) // TODO: needed?
// debug.patmat("subtp"+(subTpApprox <:< tpApprox, subTpApprox, tpApprox))
if (subTpApprox <:< tpApprox) Some(checkableType(subTp))
else None
}
}
def enumerateSubtypes(tp: Type, grouped: Boolean): List[List[Type]] = tp.typeSymbol match {
case UnitClass => List(List(UnitTpe))
case BooleanClass => List(List(ConstantTrue, ConstantFalse))
case sym if sym.isModuleClass => List(List(tp))
case sym if sym.isRefinementClass => enumerateRefinement(tp, grouped)
case sym if sym.isSealed => enumerateSealed(tp, grouped)
case sym if sym.isCase => List(List(tp))
case sym => debug.patmatResult(s"enum unsealed tp=$tp sym=$sym")(Nil)
}

if(grouped) {
def enumerateChildren(sym: Symbol) = {
sym.sealedChildren.toList
.sortBy(_.sealedSortName)
.filterNot(x => (x.isSealed || x.isPrivate) && x.isAbstractClass && !isPrimitiveValueClass(x))
}
private def enumerateRefinement(tp: Type, grouped: Boolean) = {
val parentSubtypes = tp.parents.flatMap(parent => enumerateSubtypes(parent, grouped))
if (parentSubtypes.exists(_.nonEmpty)) {
// If any of the parents is enumerable, then the refinement type is enumerable.
// We must only include subtypes of the parents that conform to `tp`.
// See neg/virtpatmat_exhaust_compound.scala for an example.
parentSubtypes.map(_.filter(_ <:< tp))
} else Nil
}

// enumerate only direct subclasses,
// subclasses of subclasses are enumerated in the next iteration
// and added to a new group
@tailrec
def groupChildren(wl: List[Symbol],
acc: List[List[Type]]): List[List[Type]] = wl match {
case hd :: tl =>
val children = enumerateChildren(hd)
// put each trait in a new group, since traits could belong to the same
// group as a derived class
val (traits, nonTraits) = children.partition(_.isTrait)
val filtered = (traits.map(List(_)) ++ List(nonTraits)).map(filterChildren)
groupChildren(tl ++ children, acc ++ filtered)
case Nil => acc
}
private def enumerateSealed(tp: Type, grouped: Boolean): List[List[Type]] = {
val tpApprox = analyzer.approximateAbstracts(tp)
val pre = tpApprox.prefix
val sym = tp.typeSymbol

def subclassesToSubtypes(syms: List[Symbol]): List[Type] = syms.flatMap { sym =>
// have to filter out children which cannot match: see ticket #3683 for an example
// compare to the fully known type `tp` (modulo abstract types),
// so that we can rule out stuff like:
// sealed trait X[T]; class XInt extends X[Int]
// XInt not valid when enumerating X[String]
// however, must also approximate abstract types
val memberType = nestedMemberType(sym, pre, tpApprox.typeSymbol.owner)
val subTp = appliedType(memberType, WildcardType.fillList(sym.typeParams.length))
val subTpApprox = analyzer.approximateAbstracts(subTp)
if (subTpApprox <:< tpApprox) Some(checkableType(subTp)) else None
}

groupChildren(sym :: Nil, Nil)
} else {
val subclasses = debug.patmatResult(s"enum $sym sealed, subclasses")(
// symbols which are both sealed and abstract need not be covered themselves, because
// all of their children must be and they cannot otherwise be created.
sym.sealedDescendants.toList
sortBy (_.sealedSortName)
filterNot (x => (x.isSealed || x.isPrivate) && x.isAbstractClass && !isPrimitiveValueClass(x))
)

List(debug.patmatResult(s"enum sealed tp=$tp, tpApprox=$tpApprox as") {
// valid subtypes are turned into checkable types, as we are entering the realm of the dynamic
filterChildren(subclasses)
})
}
case sym if sym.isCase =>
List(List(tp))
def filterAndSortChildren(children: Set[Symbol]) = {
// symbols which are both sealed and abstract need not be covered themselves,
// because all of their children must be and they cannot otherwise be created.
children.toList.filter(x => !(x.isSealed || x.isPrivate) || !x.isAbstractClass || isPrimitiveValueClass(x))
.sortBy(_.sealedSortName)
}

case sym =>
debug.patmat("enum unsealed "+ ((tp, sym, sym.isSealed, isPrimitiveValueClass(sym))))
Nil
@tailrec def groupChildren(wl: List[Symbol], acc: List[List[Symbol]]): List[List[Symbol]] = wl match {
case Nil => acc
case hd :: tl =>
val children = filterAndSortChildren(hd.sealedChildren)
// put each trait in a new group since traits could belong to the same group as a derived class
val (traits, nonTraits) = children.partition(_.isTrait)
groupChildren(tl ::: children, acc ::: traits.map(List(_)).appended(nonTraits))
}

val subclasses = debug.patmatResult(s"enum $sym sealed, subclasses") {
if (grouped) groupChildren(List(sym), Nil)
else List(filterAndSortChildren(sym.sealedDescendants))
}

debug.patmatResult(s"enum $sym sealed tp=$tp tpApprox=$tpApprox, subtypes") {
// A valid subtype is turned into a checkable type, as we are entering the realm of the dynamic
subclasses.map(subclassesToSubtypes)
}
}

// approximate a type to the static type that is fully checkable at run time,
// hiding statically known but dynamically uncheckable information using existential quantification
// TODO: this is subject to the availability of TypeTags (since an abstract type with a type tag is checkable at run time)
Expand All @@ -187,19 +164,14 @@ trait TreeAndTypeAnalysis extends Debugging {
mapOver(tp)
}
}
val result = typeArgsToWildcardsExceptArray(tp)
debug.patmatResult(s"checkableType($tp)")(result)
debug.patmatResult(s"checkableType($tp)")(typeArgsToWildcardsExceptArray(tp))
}

// a type is "uncheckable" (for exhaustivity) if we don't statically know its subtypes (i.e., it's unsealed)
// we consider tuple types with at least one component of a checkable type as a checkable type
// A type is "uncheckable" (for exhaustivity) if we don't statically know its subtypes (i.e., it's unsealed)
// A tuple of all uncheckable types is uncheckable
def uncheckableType(tp: Type): Boolean = {
val checkable = {
if (isTupleType(tp)) tupleComponents(tp).exists(tp => !uncheckableType(tp))
else enumerateSubtypes(tp, grouped = false).nonEmpty
}
// if (!checkable) debug.patmat("deemed uncheckable: "+ tp)
!checkable
if (isTupleType(tp)) tupleComponents(tp).forall(uncheckableType)
else enumerateSubtypes(tp, grouped = false).isEmpty
}
}
}
Expand Down

0 comments on commit 710de52

Please sign in to comment.