Skip to content

Commit 5a21922

Browse files
committed
Space: Remove SpaceLogic and make SpaceEngine stateless
1 parent a0ec2dc commit 5a21922

File tree

3 files changed

+60
-93
lines changed

3 files changed

+60
-93
lines changed

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ package dotty.tools
22
package dotc
33
package transform
44

5-
import scala.annotation.tailrec
65
import core._
76
import MegaPhase._
8-
import collection.mutable
97
import Symbols._, Contexts._, Types._, StdNames._, NameOps._
8+
import patmat.SpaceEngine
109
import util.Spans._
1110
import typer.Applications.*
1211
import SymUtils._
@@ -16,9 +15,12 @@ import Decorators._
1615
import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName}
1716
import config.Printers.patmatch
1817
import reporting._
19-
import dotty.tools.dotc.ast._
18+
import ast._
2019
import util.Property._
2120

21+
import scala.annotation.tailrec
22+
import scala.collection.mutable
23+
2224
/** The pattern matching transform.
2325
* After this phase, the only Match nodes remaining in the code are simple switches
2426
* where every pattern is an integer or string constant
@@ -45,9 +47,8 @@ class PatternMatcher extends MiniPhase {
4547
val translated = new Translator(matchType, this).translateMatch(tree)
4648

4749
// check exhaustivity and unreachability
48-
val engine = new patmat.SpaceEngine
49-
engine.checkExhaustivity(tree)
50-
engine.checkRedundancy(tree)
50+
SpaceEngine.checkExhaustivity(tree)
51+
SpaceEngine.checkRedundancy(tree)
5152

5253
translated.ensureConforms(matchType)
5354
}

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

Lines changed: 51 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ import transform.SymUtils._
2222
import reporting._
2323
import config.Printers.{exhaustivity => debug}
2424
import util.{SrcPos, NoSourcePosition}
25-
import collection.mutable
2625

27-
/** Space logic for checking exhaustivity and unreachability of pattern matching
26+
import scala.collection.mutable
27+
28+
/* Space logic for checking exhaustivity and unreachability of pattern matching
2829
*
2930
* Space can be thought of as a set of possible values. A type or a pattern
3031
* both refer to spaces. The space of a type is the values that inhabit the
@@ -53,16 +54,17 @@ import collection.mutable
5354
*
5455
*/
5556

56-
5757
/** space definition */
5858
sealed trait Space:
59+
import SpaceEngine.*
60+
5961
private val isSubspaceCache = mutable.HashMap.empty[Space, Boolean]
6062

61-
def isSubspace(b: Space)(engine: SpaceEngine)(using Context): Boolean =
63+
def isSubspace(b: Space)(using Context): Boolean =
6264
if this == Empty then true
6365
else if b == Empty then false
64-
else trace(s"isSubspace(${engine.show(this)}, ${engine.show(b)})", debug) {
65-
isSubspaceCache.getOrElseUpdate(b, engine.computeIsSubspace(this, b))
66+
else trace(s"isSubspace(${show(this)}, ${show(b)})", debug) {
67+
isSubspaceCache.getOrElseUpdate(b, computeIsSubspace(this, b))
6668
}
6769
end Space
6870

@@ -83,44 +85,8 @@ case class Prod(tp: Type, unappTp: TermRef, params: List[Space]) extends Space
8385
/** Union of spaces */
8486
case class Or(spaces: Seq[Space]) extends Space
8587

86-
/** abstract space logic */
87-
trait SpaceLogic {
88-
/** Is `tp1` a subtype of `tp2`? */
89-
def isSubType(tp1: Type, tp2: Type): Boolean
90-
91-
/** True if we can assume that the two unapply methods are the same.
92-
* That is, given the same parameter, they return the same result.
93-
*
94-
* We assume that unapply methods are pure, but the same method may
95-
* be called with different prefixes, thus behaving differently.
96-
*/
97-
def isSameUnapply(tp1: TermRef, tp2: TermRef): Boolean
98-
99-
/** Return a space containing the values of both types.
100-
*
101-
* The types should be atomic (non-decomposable) and unrelated (neither
102-
* should be a subtype of the other).
103-
*/
104-
def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type): Space
105-
106-
/** Is the type `tp` decomposable? i.e. all values of the type can be covered
107-
* by its decomposed types.
108-
*
109-
* Abstract sealed class, OrType, Boolean and Java enums can be decomposed.
110-
*/
111-
def canDecompose(tp: Type): Boolean
112-
113-
/** Return term parameter types of the extractor `unapp` */
114-
def signature(unapp: TermRef, scrutineeTp: Type, argLen: Int): List[Type]
115-
116-
/** Get components of decomposable types */
117-
def decompose(tp: Type): List[Typ]
118-
119-
/** Whether the extractor covers the given type */
120-
def covers(unapp: TermRef, scrutineeTp: Type, argLen: Int): Boolean
121-
122-
/** Display space in string format */
123-
def show(sp: Space): String
88+
object SpaceEngine {
89+
import tpd._
12490

12591
/** Simplify space such that a space equal to `Empty` becomes `Empty` */
12692
def simplify(space: Space)(using Context): Space = trace(s"simplify ${show(space)} --> ", debug, show)(space match {
@@ -174,7 +140,9 @@ trait SpaceLogic {
174140
}
175141

176142
/** Is `a` a subspace of `b`? Equivalent to `simplify(simplify(a) - simplify(b)) == Empty`, but faster */
177-
def isSubspace(a: Space, b: Space)(using Context): Boolean = trace(s"isSubspace(${show(a)}, ${show(b)})", debug) {
143+
def isSubspace(a: Space, b: Space)(using Context): Boolean = a.isSubspace(b)
144+
145+
def computeIsSubspace(a: Space, b: Space)(using Context): Boolean = {
178146
def tryDecompose1(tp: Type) = canDecompose(tp) && isSubspace(Or(decompose(tp)), b)
179147
def tryDecompose2(tp: Type) = canDecompose(tp) && isSubspace(a, Or(decompose(tp)))
180148

@@ -302,9 +270,6 @@ trait SpaceLogic {
302270
Or(spaces)
303271
}
304272
}
305-
}
306-
307-
object SpaceEngine {
308273

309274
/** Is the unapply or unapplySeq irrefutable?
310275
* @param unapp The unapply function reference
@@ -352,13 +317,13 @@ object SpaceEngine {
352317

353318
case _ => false
354319
}
355-
}
356-
357-
/** Scala implementation of space logic */
358-
class SpaceEngine(using Context) extends SpaceLogic {
359-
import tpd._
360320

361-
override def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type): Space = trace(s"atomic intersection: ${AndType(tp1, tp2).show}", debug) {
321+
/** Return a space containing the values of both types.
322+
*
323+
* The types should be atomic (non-decomposable) and unrelated (neither
324+
* should be a subtype of the other).
325+
*/
326+
def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type)(using Context): Space = trace(i"atomic intersection: ${AndType(tp1, tp2)}", debug) {
362327
// Precondition: !isSubType(tp1, tp2) && !isSubType(tp2, tp1).
363328
if !ctx.mode.is(Mode.SafeNulls) && (tp1.isNullType || tp2.isNullType) then
364329
// Since projections of types don't include null, intersection with null is empty.
@@ -373,7 +338,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
373338
}
374339

375340
/** Return the space that represents the pattern `pat` */
376-
def project(pat: Tree): Space = pat match {
341+
def project(pat: Tree)(using Context): Space = pat match {
377342
case Literal(c) =>
378343
if (c.value.isInstanceOf[Symbol])
379344
Typ(c.value.asInstanceOf[Symbol].termRef, decomposed = false)
@@ -436,12 +401,12 @@ class SpaceEngine(using Context) extends SpaceLogic {
436401
Typ(pat.tpe.narrow, decomposed = false)
437402
}
438403

439-
private def project(tp: Type): Space = tp match {
404+
private def project(tp: Type)(using Context): Space = tp match {
440405
case OrType(tp1, tp2) => Or(project(tp1) :: project(tp2) :: Nil)
441406
case tp => Typ(tp, decomposed = true)
442407
}
443408

444-
private def unapplySeqInfo(resTp: Type, pos: SrcPos): (Int, Type, Type) = {
409+
private def unapplySeqInfo(resTp: Type, pos: SrcPos)(using Context): (Int, Type, Type) = {
445410
var resultTp = resTp
446411
var elemTp = unapplySeqTypeElemTp(resultTp)
447412
var arity = productArity(resultTp, pos)
@@ -488,7 +453,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
488453
* If `isValue` is true, then pattern-bound symbols are erased to its upper bound.
489454
* This is needed to avoid spurious unreachable warnings. See tests/patmat/i6197.scala.
490455
*/
491-
private def erase(tp: Type, inArray: Boolean = false, isValue: Boolean = false): Type = trace(i"$tp erased to", debug) {
456+
private def erase(tp: Type, inArray: Boolean = false, isValue: Boolean = false)(using Context): Type = trace(i"$tp erased to", debug) {
492457

493458
tp match {
494459
case tp @ AppliedType(tycon, args) if tycon.typeSymbol.isPatternBound =>
@@ -520,7 +485,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
520485

521486
/** Space of the pattern: unapplySeq(a, b, c: _*)
522487
*/
523-
def projectSeq(pats: List[Tree]): Space = {
488+
def projectSeq(pats: List[Tree])(using Context): Space = {
524489
if (pats.isEmpty) return Typ(defn.NilType, false)
525490

526491
val (items, zero) = if (isWildcardStarArg(pats.last))
@@ -535,29 +500,30 @@ class SpaceEngine(using Context) extends SpaceLogic {
535500
}
536501
}
537502

538-
def isPrimToBox(tp: Type, pt: Type): Boolean =
503+
def isPrimToBox(tp: Type, pt: Type)(using Context): Boolean =
539504
tp.isPrimitiveValueType && (defn.boxedType(tp).classSymbol eq pt.classSymbol)
540505

541-
private val isSubspaceCache = mutable.HashMap.empty[(Space, Space, Context), Boolean]
542-
543-
override def isSubspace(a: Space, b: Space)(using Context): Boolean = a.isSubspace(b)(this)
544-
545-
def computeIsSubspace(a: Space, b: Space)(using Context): Boolean = super.isSubspace(a, b)
546-
547506
/** Is `tp1` a subtype of `tp2`? */
548-
def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) {
507+
def isSubType(tp1: Type, tp2: Type)(using Context): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) {
549508
if tp1 == ConstantType(Constant(null)) && !ctx.mode.is(Mode.SafeNulls)
550509
then tp2 == ConstantType(Constant(null))
551510
else tp1 <:< tp2
552511
}
553512

554-
def isSameUnapply(tp1: TermRef, tp2: TermRef): Boolean =
513+
/** True if we can assume that the two unapply methods are the same.
514+
* That is, given the same parameter, they return the same result.
515+
*
516+
* We assume that unapply methods are pure, but the same method may
517+
* be called with different prefixes, thus behaving differently.
518+
*/
519+
def isSameUnapply(tp1: TermRef, tp2: TermRef)(using Context): Boolean =
555520
// always assume two TypeTest[S, T].unapply are the same if they are equal in types
556521
(tp1.prefix.isStable && tp2.prefix.isStable || tp1.symbol == defn.TypeTest_unapply)
557522
&& tp1 =:= tp2
558523

559-
/** Parameter types of the case class type `tp`. Adapted from `unapplyPlan` in patternMatcher */
560-
def signature(unapp: TermRef, scrutineeTp: Type, argLen: Int): List[Type] = {
524+
/** Return term parameter types of the extractor `unapp`.
525+
* Parameter types of the case class type `tp`. Adapted from `unapplyPlan` in patternMatcher */
526+
def signature(unapp: TermRef, scrutineeTp: Type, argLen: Int)(using Context): List[Type] = {
561527
val unappSym = unapp.symbol
562528

563529
// println("scrutineeTp = " + scrutineeTp.show)
@@ -620,14 +586,14 @@ class SpaceEngine(using Context) extends SpaceLogic {
620586
}
621587

622588
/** Whether the extractor covers the given type */
623-
def covers(unapp: TermRef, scrutineeTp: Type, argLen: Int): Boolean =
589+
def covers(unapp: TermRef, scrutineeTp: Type, argLen: Int)(using Context): Boolean =
624590
SpaceEngine.isIrrefutable(unapp, argLen) || unapp.symbol == defn.TypeTest_unapply && {
625591
val AppliedType(_, _ :: tp :: Nil) = unapp.prefix.widen.dealias: @unchecked
626592
scrutineeTp <:< tp
627593
}
628594

629595
/** Decompose a type into subspaces -- assume the type can be decomposed */
630-
def decompose(tp: Type): List[Typ] = trace(i"decompose($tp)", debug, show(_: Seq[Space])) {
596+
def decompose(tp: Type)(using Context): List[Typ] = trace(i"decompose($tp)", debug, showSpaces) {
631597
def rec(tp: Type, mixins: List[Type]): List[Typ] = tp.dealias match {
632598
case AndType(tp1, tp2) =>
633599
def decomposeComponent(tpA: Type, tpB: Type): List[Typ] =
@@ -708,7 +674,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
708674
}
709675

710676
/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
711-
def canDecompose(tp: Type): Boolean =
677+
def canDecompose(tp: Type)(using Context): Boolean =
712678
val res = tp.dealias match
713679
case AppliedType(tycon, _) if canDecompose(tycon) => true
714680
case tp: NamedType if canDecompose(tp.prefix) => true
@@ -735,7 +701,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
735701
* C --> C if current owner is C !!!
736702
*
737703
*/
738-
def showType(tp: Type, showTypeArgs: Boolean = false): String = {
704+
def showType(tp: Type, showTypeArgs: Boolean = false)(using Context): String = {
739705
val enclosingCls = ctx.owner.enclosingClass
740706

741707
def isOmittable(sym: Symbol) =
@@ -776,7 +742,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
776742
}
777743

778744
/** Whether the counterexample is satisfiable. The space is flattened and non-empty. */
779-
def satisfiable(sp: Space): Boolean = {
745+
def satisfiable(sp: Space)(using Context): Boolean = {
780746
def impossible: Nothing = throw new AssertionError("`satisfiable` only accepts flattened space.")
781747

782748
def genConstraint(space: Space): List[(Type, Type)] = space match {
@@ -807,10 +773,10 @@ class SpaceEngine(using Context) extends SpaceLogic {
807773
checkConstraint(genConstraint(sp))(using ctx.fresh.setNewTyperState())
808774
}
809775

810-
def show(ss: Seq[Space]): String = ss.map(show).mkString(", ")
776+
def showSpaces(ss: Seq[Space])(using Context): String = ss.map(show).mkString(", ")
811777

812778
/** Display spaces */
813-
def show(s: Space): String = {
779+
def show(s: Space)(using Context): String = {
814780
def params(tp: Type): List[Type] = tp.classSymbol.primaryConstructor.info.firstParamTypes
815781

816782
/** does the companion object of the given symbol have custom unapply */
@@ -862,7 +828,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
862828
doShow(s, flattenList = false)
863829
}
864830

865-
private def exhaustivityCheckable(sel: Tree): Boolean = {
831+
private def exhaustivityCheckable(sel: Tree)(using Context): Boolean = {
866832
val seen = collection.mutable.Set.empty[Type]
867833

868834
// Possible to check everything, but be compatible with scalac by default
@@ -891,8 +857,8 @@ class SpaceEngine(using Context) extends SpaceLogic {
891857
res
892858
}
893859

894-
/** Whehter counter-examples should be further checked? True for GADTs. */
895-
private def shouldCheckExamples(tp: Type): Boolean =
860+
/** Whether counter-examples should be further checked? True for GADTs. */
861+
private def shouldCheckExamples(tp: Type)(using Context): Boolean =
896862
new TypeAccumulator[Boolean] {
897863
override def apply(b: Boolean, tp: Type): Boolean = tp match {
898864
case tref: TypeRef if tref.symbol.is(TypeParam) && variance != 1 => true
@@ -903,7 +869,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
903869
/** Return the underlying type of non-module, non-constant, non-enum case singleton types.
904870
* Also widen ExprType to its result type, and rewrap any annotation wrappers.
905871
* For example, with `val opt = None`, widen `opt.type` to `None.type`. */
906-
def toUnderlying(tp: Type): Type = trace(i"toUnderlying($tp)", show = true)(tp match {
872+
def toUnderlying(tp: Type)(using Context): Type = trace(i"toUnderlying($tp)", show = true)(tp match {
907873
case _: ConstantType => tp
908874
case tp: TermRef if tp.symbol.is(Module) => tp
909875
case tp: TermRef if tp.symbol.isAllOf(EnumCase) => tp
@@ -913,7 +879,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
913879
case _ => tp
914880
})
915881

916-
def checkExhaustivity(_match: Match): Unit = {
882+
def checkExhaustivity(_match: Match)(using Context): Unit = {
917883
val Match(sel, cases) = _match
918884
debug.println(i"checking exhaustivity of ${_match}")
919885

@@ -939,10 +905,10 @@ class SpaceEngine(using Context) extends SpaceLogic {
939905
if uncovered.nonEmpty then
940906
val hasMore = uncovered.lengthCompare(6) > 0
941907
val deduped = dedup(uncovered.take(6))
942-
report.warning(PatternMatchExhaustivity(show(deduped), hasMore), sel.srcPos)
908+
report.warning(PatternMatchExhaustivity(showSpaces(deduped), hasMore), sel.srcPos)
943909
}
944910

945-
private def redundancyCheckable(sel: Tree): Boolean =
911+
private def redundancyCheckable(sel: Tree)(using Context): Boolean =
946912
// Ignore Expr[T] and Type[T] for unreachability as a special case.
947913
// Quote patterns produce repeated calls to the same unapply method, but with different implicit parameters.
948914
// Since we assume that repeated calls to the same unapply method overlap
@@ -952,7 +918,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
952918
&& !sel.tpe.widen.isRef(defn.QuotedExprClass)
953919
&& !sel.tpe.widen.isRef(defn.QuotedTypeClass)
954920

955-
def checkRedundancy(_match: Match): Unit = {
921+
def checkRedundancy(_match: Match)(using Context): Unit = {
956922
val Match(sel, _) = _match
957923
val cases = _match.cases.toIndexedSeq
958924
debug.println(i"checking redundancy in $_match")

compiler/test/dotty/tools/dotc/transform/patmat/SpaceEngineTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import vulpix.TestConfiguration, TestConfiguration.basicClasspath
1111
import org.junit, junit.Test, junit.Assert.*
1212

1313
class SpaceEngineTest:
14+
import SpaceEngine.*
15+
1416
@Test def isSubspaceTest1: Unit = inCompilerContext(basicClasspath) {
1517
// Testing the property of `isSubspace` that:
1618
// isSubspace(a, b) <=> simplify(simplify(a) - simplify(a)) == Empty
1719
// Previously there were no simplify calls,
1820
// and this is a counter-example,
1921
// for which you need either to simplify(b) or simplify the minus result.
20-
val engine = patmat.SpaceEngine()
21-
import engine.*
2222

2323
val tp = defn.ConsType.appliedTo(defn.AnyType)
2424
val unappTp = requiredMethod("scala.collection.immutable.::.unapply").termRef

0 commit comments

Comments
 (0)