From 84b55b159cfbaa5534575a037c9000a923218007 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 29 Jan 2021 22:04:42 +0000 Subject: [PATCH 1/2] Drop "!grouped" guards in 3 cases of enumerateSubtypes They seem unnecessary, or even wrong, to me. I'm not even sure if these cases get picked up by later, unguarded, ones or if it hits the Nil default. Either way, these cases should always apply. --- .../tools/nsc/transform/patmat/MatchAnalysis.scala | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index a531726fe395..dde820d3208d 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -84,15 +84,10 @@ trait TreeAndTypeAnalysis extends Debugging { // 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 => + case UnitClass => List(List(UnitTpe)) + case BooleanClass => List(List(ConstantTrue, ConstantFalse)) + case _: ModuleClassSymbol => List(List(tp)) + case _: 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. From 94e00fbfa87e08fe92988c72fb8e398ee47645bd Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 29 Jan 2021 11:48:00 +0000 Subject: [PATCH 2/2] patmat: Break up enumerateSubtypes --- .../nsc/transform/patmat/MatchAnalysis.scala | 151 ++++++++---------- 1 file changed, 64 insertions(+), 87 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index dde820d3208d..2be8a7b6bc67 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -81,89 +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 { - case UnitClass => List(List(UnitTpe)) - case BooleanClass => List(List(ConstantTrue, ConstantFalse)) - case _: ModuleClassSymbol => List(List(tp)) - case _: 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) @@ -182,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 } } }