Skip to content

Commit 0e8f05d

Browse files
authored
Merge pull request #1364 from dotty-staging/exhaustivity2
Implementation of exhaustivity and redundancy check
2 parents 265ade0 + cc02243 commit 0e8f05d

File tree

147 files changed

+3883
-17
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+3883
-17
lines changed

src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -616,16 +616,20 @@ object desugar {
616616
*
617617
* { cases }
618618
* ==>
619-
* x$1 => x$1 match { cases }
619+
* x$1 => (x$1 @unchecked) match { cases }
620620
*
621621
* If `nparams` != 1, expand instead to
622622
*
623-
* (x$1, ..., x$n) => (x$0, ..., x${n-1}) match { cases }
623+
* (x$1, ..., x$n) => (x$0, ..., x${n-1} @unchecked) match { cases }
624624
*/
625-
def makeCaseLambda(cases: List[CaseDef], nparams: Int = 1)(implicit ctx: Context) = {
625+
def makeCaseLambda(cases: List[CaseDef], nparams: Int = 1, unchecked: Boolean = true)(implicit ctx: Context) = {
626626
val params = (1 to nparams).toList.map(makeSyntheticParameter(_))
627627
val selector = makeTuple(params.map(p => Ident(p.name)))
628-
Function(params, Match(selector, cases))
628+
629+
if (unchecked)
630+
Function(params, Match(Annotated(New(ref(defn.UncheckedAnnotType)), selector), cases))
631+
else
632+
Function(params, Match(selector, cases))
629633
}
630634

631635
/** Map n-ary function `(p1, ..., pn) => body` where n != 1 to unary function as follows:
@@ -753,7 +757,7 @@ object desugar {
753757
case VarPattern(named, tpt) =>
754758
Function(derivedValDef(named, tpt, EmptyTree, Modifiers(Param)) :: Nil, body)
755759
case _ =>
756-
makeCaseLambda(CaseDef(pat, EmptyTree, body) :: Nil)
760+
makeCaseLambda(CaseDef(pat, EmptyTree, body) :: Nil, unchecked = false)
757761
}
758762

759763
/** If `pat` is not an Identifier, a Typed(Ident, _), or a Bind, wrap
@@ -799,7 +803,7 @@ object desugar {
799803
val cases = List(
800804
CaseDef(pat, EmptyTree, Literal(Constant(true))),
801805
CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false))))
802-
Apply(Select(rhs, nme.withFilter), Match(EmptyTree, cases))
806+
Apply(Select(rhs, nme.withFilter), makeCaseLambda(cases))
803807
}
804808

805809
/** Is pattern `pat` irrefutable when matched against `rhs`?

src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class ScalaSettings extends Settings.SettingGroup {
163163
val YkeepComments = BooleanSetting("-Ykeep-comments", "Keep comments when scanning source files.")
164164
val YforceSbtPhases = BooleanSetting("-Yforce-sbt-phases", "Run the phases used by sbt for incremental compilation (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of sbt, for debugging.")
165165
val YdumpSbtInc = BooleanSetting("-Ydump-sbt-inc", "For every compiled foo.scala, output the API representation and dependencies used for sbt incremental compilation in foo.inc, implies -Yforce-sbt-phases.")
166+
val YcheckAllPatmat = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm)")
166167
def stop = YstopAfter
167168

168169
/** Area-specific debug output.

src/dotty/tools/dotc/core/Types.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,13 @@ object Types {
841841
case _ => this
842842
}
843843

844+
/** Eliminate anonymous classes */
845+
final def deAnonymize(implicit ctx: Context): Type = this match {
846+
case tp:TypeRef if tp.symbol.isAnonymousClass =>
847+
tp.symbol.asClass.typeRef.asSeenFrom(tp.prefix, tp.symbol.owner)
848+
case tp => tp
849+
}
850+
844851
/** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type
845852
* is no longer alias type, LazyRef, or instantiated type variable.
846853
*/

src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class ClassfileParser(
8585
val jflags = in.nextChar
8686
val isAnnotation = hasAnnotation(jflags)
8787
val sflags = classTranslation.flags(jflags)
88+
val isEnum = (jflags & JAVA_ACC_ENUM) != 0
8889
val nameIdx = in.nextChar
8990
currentClassName = pool.getClassName(nameIdx)
9091

@@ -140,6 +141,15 @@ class ClassfileParser(
140141
setClassInfo(classRoot, classInfo)
141142
setClassInfo(moduleRoot, staticInfo)
142143
}
144+
145+
// eager load java enum definitions for exhaustivity check of pattern match
146+
if (isEnum) {
147+
instanceScope.toList.map(_.ensureCompleted())
148+
staticScope.toList.map(_.ensureCompleted())
149+
classRoot.setFlag(Flags.Enum)
150+
moduleRoot.setFlag(Flags.Enum)
151+
}
152+
143153
result
144154
}
145155

src/dotty/tools/dotc/transform/ExpandSAMs.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
7474
Bind(defaultSym, Underscore(selector.tpe.widen)),
7575
EmptyTree,
7676
Literal(Constant(false)))
77-
cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase)
77+
val annotated = Annotated(New(ref(defn.UncheckedAnnotType)), paramRef)
78+
cpy.Match(applyRhs)(annotated, cases.map(translateCase) :+ defaultCase)
7879
case _ =>
7980
tru
8081
}

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Applications._
2424
import TypeApplications._
2525
import SymUtils._, core.NameOps._
2626
import core.Mode
27+
import patmat._
2728

2829
import dotty.tools.dotc.util.Positions.Position
2930
import dotty.tools.dotc.core.Decorators._
@@ -52,6 +53,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
5253
override def transformMatch(tree: Match)(implicit ctx: Context, info: TransformerInfo): Tree = {
5354
val translated = new Translator()(ctx).translator.translateMatch(tree)
5455

56+
// check exhaustivity and unreachability
57+
val engine = new SpaceEngine
58+
if (engine.checkable(tree)) {
59+
engine.checkExhaustivity(tree)
60+
engine.checkRedundancy(tree)
61+
}
62+
5563
translated.ensureConforms(tree.tpe)
5664
}
5765

@@ -1244,13 +1252,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
12441252
case _ => false
12451253
}
12461254

1247-
def elimAnonymousClass(t: Type) = t match {
1248-
case t:TypeRef if t.symbol.isAnonymousClass =>
1249-
t.symbol.asClass.typeRef.asSeenFrom(t.prefix, t.symbol.owner)
1250-
case _ =>
1251-
t
1252-
}
1253-
12541255
/** Implement a pattern match by turning its cases (including the implicit failure case)
12551256
* into the corresponding (monadic) extractors, and combining them with the `orElse` combinator.
12561257
*
@@ -1264,7 +1265,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
12641265
def translateMatch(match_ : Match): Tree = {
12651266
val Match(sel, cases) = match_
12661267

1267-
val selectorTp = elimAnonymousClass(sel.tpe.widen/*withoutAnnotations*/)
1268+
val selectorTp = sel.tpe.widen.deAnonymize/*withoutAnnotations*/
12681269

12691270
val selectorSym = freshSym(sel.pos, selectorTp, "selector")
12701271

@@ -1273,6 +1274,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
12731274
case _ => (cases, None)
12741275
}
12751276

1277+
12761278
// checkMatchVariablePatterns(nonSyntheticCases) // only used for warnings
12771279

12781280
// we don't transform after uncurry

src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import Symbols._, TypeUtils._
3939
*
4040
* (9) Adds SourceFile annotations to all top-level classes and objects
4141
*
42+
* (10) Adds Child annotations to all sealed classes
43+
*
4244
* The reason for making this a macro transform is that some functions (in particular
4345
* super and protected accessors and instantiation checks) are naturally top-down and
4446
* don't lend themselves to the bottom-up approach of a mini phase. The other two functions
@@ -243,6 +245,13 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
243245
ctx.compilationUnit.source.exists &&
244246
sym != defn.SourceFileAnnot)
245247
sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path))
248+
249+
if (!sym.isAnonymousClass) // ignore anonymous class
250+
for (parent <- sym.asClass.classInfo.classParents) {
251+
val pclazz = parent.classSymbol
252+
if (pclazz.is(Sealed)) pclazz.addAnnotation(Annotation.makeChild(sym))
253+
}
254+
246255
tree
247256
}
248257
else {

0 commit comments

Comments
 (0)