Skip to content

Commit a913f77

Browse files
committed
refactor inhabited in exhaustivness check
1 parent b58799e commit a913f77

File tree

1 file changed

+93
-92
lines changed
  • compiler/src/dotty/tools/dotc/transform/patmat

1 file changed

+93
-92
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 93 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -278,21 +278,8 @@ trait SpaceLogic {
278278
}
279279
}
280280

281-
object SpaceEngine {
282-
private sealed trait Implementability {
283-
def show(implicit ctx: Context) = this match {
284-
case SubclassOf(classSyms) => s"SubclassOf(${classSyms.map(_.show)})"
285-
case other => other.toString
286-
}
287-
}
288-
private case object ClassOrTrait extends Implementability
289-
private case class SubclassOf(classSyms: List[Symbol]) extends Implementability
290-
private case object Unimplementable extends Implementability
291-
}
292-
293281
/** Scala implementation of space logic */
294282
class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
295-
import SpaceEngine._
296283
import tpd._
297284

298285
private val scalaSomeClass = ctx.requiredClass("scala.Some")
@@ -301,77 +288,15 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
301288
private val scalaNilType = ctx.requiredModuleRef("scala.collection.immutable.Nil")
302289
private val scalaConsType = ctx.requiredClassRef("scala.collection.immutable.::")
303290

304-
/** Checks if it's possible to create a trait/class which is a subtype of `tp`.
305-
*
306-
* - doesn't handle member collisions (will not declare a type unimplementable because of one)
307-
* - expects that neither Any nor Object reach it
308-
* (this is currently true due to both isSubType and and/or type simplification)
309-
*
310-
* See [[intersectUnrelatedAtomicTypes]].
311-
*/
312-
private def implementability(tp: Type): Implementability = tp.dealias match {
313-
case AndType(tp1, tp2) =>
314-
(implementability(tp1), implementability(tp2)) match {
315-
case (Unimplementable, _) | (_, Unimplementable) => Unimplementable
316-
case (SubclassOf(classSyms1), SubclassOf(classSyms2)) =>
317-
(for {
318-
sym1 <- classSyms1
319-
sym2 <- classSyms2
320-
result <-
321-
if (sym1 isSubClass sym2) List(sym1)
322-
else if (sym2 isSubClass sym1) List(sym2)
323-
else Nil
324-
} yield result) match {
325-
case Nil => Unimplementable
326-
case lst => SubclassOf(lst)
327-
}
328-
case (ClassOrTrait, ClassOrTrait) => ClassOrTrait
329-
case (SubclassOf(clss), _) => SubclassOf(clss)
330-
case (_, SubclassOf(clss)) => SubclassOf(clss)
331-
}
332-
case OrType(tp1, tp2) =>
333-
(implementability(tp1), implementability(tp2)) match {
334-
case (ClassOrTrait, _) | (_, ClassOrTrait) => ClassOrTrait
335-
case (SubclassOf(classSyms1), SubclassOf(classSyms2)) =>
336-
SubclassOf(classSyms1 ::: classSyms2)
337-
case (SubclassOf(classSyms), _) => SubclassOf(classSyms)
338-
case (_, SubclassOf(classSyms)) => SubclassOf(classSyms)
339-
case _ => Unimplementable
340-
}
341-
case _: SingletonType =>
342-
// singleton types have no instantiable subtypes
343-
Unimplementable
344-
case tp: RefinedType =>
345-
// refinement itself is not considered - it would at most make
346-
// a type unimplementable because of a member collision
347-
implementability(tp.parent)
348-
case other =>
349-
val classSym = other.classSymbol
350-
if (classSym.exists) {
351-
if (classSym is Final) Unimplementable
352-
else if (classSym is Trait) ClassOrTrait
353-
else SubclassOf(List(classSym))
354-
} else {
355-
// if no class symbol exists, conservatively say that anything
356-
// can implement `tp`
357-
ClassOrTrait
358-
}
359-
}
360-
361291
override def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type) = {
362292
val and = AndType(tp1, tp2)
363293
// Precondition: !(tp1 <:< tp2) && !(tp2 <:< tp1)
364294
// Then, no leaf of the and-type tree `and` is a subtype of `and`.
365-
// Then, to create a value of type `and` you must instantiate a trait (class/module)
366-
// which is a subtype of all the leaves of `and`.
367-
val imp = implementability(and)
295+
val res = inhabited(and)
368296

369-
debug.println(s"atomic intersection: ${and.show} ~ ${imp.show}")
297+
debug.println(s"atomic intersection: ${and.show} = ${res}")
370298

371-
imp match {
372-
case Unimplementable => Empty
373-
case _ => Typ(and, true)
374-
}
299+
if (res) Typ(and, true) else Empty
375300
}
376301

377302
/* Whether the extractor is irrefutable */
@@ -574,21 +499,97 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
574499

575500
/** Can this type be inhabited by a value?
576501
*
577-
* Intersection between singleton types and other types is always empty
578-
* the singleton type is not a subtype of the other type.
579-
* See patmat/i3573.scala for an example.
502+
* Check is based on the following facts:
503+
*
504+
* - single inheritance of classes
505+
* - final class cannot be extended
506+
* - intersection of a singleton type with another irrelevant type (patmat/i3574.scala)
507+
*
580508
*/
581-
def inhabited(tpe: Type)(implicit ctx: Context): Boolean = {
582-
val emptySingletonIntersection = new ExistsAccumulator({
583-
case AndType(s: SingletonType, t) =>
584-
!(s <:< t)
585-
case AndType(t, s: SingletonType) =>
586-
!(s <:< t)
587-
case x =>
588-
false
589-
})
590-
591-
!emptySingletonIntersection(false, tpe)
509+
def inhabited(tp: Type)(implicit ctx: Context): Boolean = {
510+
// convert top-level type shape into "conjunctive normal form"
511+
def cnf(tp: Type): Type = tp match {
512+
case AndType(OrType(l, r), tp) =>
513+
val tp1 = cnf(tp)
514+
val l1 = cnf(l)
515+
val r1 = cnf(r)
516+
OrType(cnf(AndType(l1, tp1)), cnf(AndType(r1, tp1)))
517+
case AndType(tp, o: OrType) =>
518+
cnf(AndType(o, tp))
519+
case AndType(l, r) =>
520+
val l1 = cnf(l)
521+
val r1 = cnf(r)
522+
if (l1.ne(l) || r1.ne(r)) cnf(AndType(l1, r1))
523+
else AndType(l1, r1)
524+
case OrType(l, r) =>
525+
OrType(cnf(l), cnf(r))
526+
case tp @ RefinedType(OrType(tp1, tp2), _, _) =>
527+
OrType(
528+
cnf(tp.derivedRefinedType(tp1, refinedName = tp.refinedName, refinedInfo = tp.refinedInfo)),
529+
cnf(tp.derivedRefinedType(tp2, refinedName = tp.refinedName, refinedInfo = tp.refinedInfo))
530+
)
531+
case tp: RefinedType =>
532+
val parent1 = cnf(tp.parent)
533+
val tp1 = tp.derivedRefinedType(parent1, refinedName = tp.refinedName, refinedInfo = tp.refinedInfo)
534+
535+
if (parent1.ne(tp.parent)) cnf(tp1) else tp1
536+
case tp: TypeAlias =>
537+
cnf(tp.alias)
538+
case _ =>
539+
tp
540+
}
541+
542+
def isSingleton(tp: Type): Boolean = tp.dealias match {
543+
case AndType(l, r) => isSingleton(l) || isSingleton(r)
544+
case OrType(l, r) => isSingleton(l) && isSingleton(r)
545+
case tp => tp.isSingleton
546+
}
547+
548+
def superType(tp: Type): Type = tp match {
549+
case tp: TypeProxy => tp.superType
550+
case OrType(tp1, tp2) => OrType(superType(tp1), superType(tp2))
551+
case AndType(tp1, tp2) => AndType(superType(tp1), superType(tp2))
552+
case _ => tp
553+
}
554+
555+
def recur(tp: Type): Boolean = tp.dealias match {
556+
case AndType(tp1, tp2) =>
557+
recur(tp1) && recur(tp2) && {
558+
val bases1 = tp1.widenDealias.classSymbols
559+
val bases2 = tp2.widenDealias.classSymbols
560+
561+
debug.println(s"bases of ${tp1.show}: " + bases1)
562+
debug.println(s"bases of ${tp2.show}: " + bases2)
563+
564+
val noClassConflict =
565+
bases1.forall(sym1 => sym1.is(Trait) || bases2.forall(sym2 => sym2.is(Trait) || sym1.isSubClass(sym2))) ||
566+
bases1.forall(sym1 => sym1.is(Trait) || bases2.forall(sym2 => sym2.is(Trait) || sym2.isSubClass(sym1)))
567+
568+
debug.println(s"class conflict for ${tp.show}? " + !noClassConflict)
569+
570+
noClassConflict &&
571+
(!isSingleton(tp1) || tp1 <:< tp2) &&
572+
(!isSingleton(tp2) || tp2 <:< tp1) &&
573+
(!bases1.exists(_ is Final) || tp1 <:< superType(tp2)) &&
574+
(!bases2.exists(_ is Final) || tp2 <:< superType(tp1))
575+
}
576+
case OrType(tp1, tp2) =>
577+
recur(tp1) || recur(tp2)
578+
case tp: RefinedType =>
579+
recur(tp.parent)
580+
case tp: TypeRef =>
581+
recur(tp.prefix) &&
582+
!(tp.classSymbol.is(Sealed) && tp.classSymbol.is(AbstractOrTrait) && tp.classSymbol.children.isEmpty) &&
583+
!(tp.classSymbol.is(AbstractFinal))
584+
case _ =>
585+
true
586+
}
587+
588+
val res = recur(cnf(tp))
589+
590+
debug.println(s"${tp.show} inhabited? " + res)
591+
592+
res
592593
}
593594

594595
/** Instantiate type `tp1` to be a subtype of `tp2`

0 commit comments

Comments
 (0)