Skip to content

Implementation of exhaustivity and redundancy check #1364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -616,16 +616,20 @@ object desugar {
*
* { cases }
* ==>
* x$1 => x$1 match { cases }
* x$1 => (x$1 @unchecked) match { cases }
*
* If `nparams` != 1, expand instead to
*
* (x$1, ..., x$n) => (x$0, ..., x${n-1}) match { cases }
* (x$1, ..., x$n) => (x$0, ..., x${n-1} @unchecked) match { cases }
*/
def makeCaseLambda(cases: List[CaseDef], nparams: Int = 1)(implicit ctx: Context) = {
def makeCaseLambda(cases: List[CaseDef], nparams: Int = 1, unchecked: Boolean = true)(implicit ctx: Context) = {
val params = (1 to nparams).toList.map(makeSyntheticParameter(_))
val selector = makeTuple(params.map(p => Ident(p.name)))
Function(params, Match(selector, cases))

if (unchecked)
Function(params, Match(Annotated(New(ref(defn.UncheckedAnnotType)), selector), cases))
else
Function(params, Match(selector, cases))
}

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

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

/** Is pattern `pat` irrefutable when matched against `rhs`?
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class ScalaSettings extends Settings.SettingGroup {
val YkeepComments = BooleanSetting("-Ykeep-comments", "Keep comments when scanning source files.")
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.")
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.")
val YcheckAllPatmat = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm)")
def stop = YstopAfter

/** Area-specific debug output.
Expand Down
7 changes: 7 additions & 0 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,13 @@ object Types {
case _ => this
}

/** Eliminate anonymous classes */
final def deAnonymize(implicit ctx: Context): Type = this match {
case tp:TypeRef if tp.symbol.isAnonymousClass =>
tp.symbol.asClass.typeRef.asSeenFrom(tp.prefix, tp.symbol.owner)
case tp => tp
}

/** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type
* is no longer alias type, LazyRef, or instantiated type variable.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/dotty/tools/dotc/core/classfile/ClassfileParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class ClassfileParser(
val jflags = in.nextChar
val isAnnotation = hasAnnotation(jflags)
val sflags = classTranslation.flags(jflags)
val isEnum = (jflags & JAVA_ACC_ENUM) != 0
val nameIdx = in.nextChar
currentClassName = pool.getClassName(nameIdx)

Expand Down Expand Up @@ -140,6 +141,15 @@ class ClassfileParser(
setClassInfo(classRoot, classInfo)
setClassInfo(moduleRoot, staticInfo)
}

// eager load java enum definitions for exhaustivity check of pattern match
if (isEnum) {
instanceScope.toList.map(_.ensureCompleted())
staticScope.toList.map(_.ensureCompleted())
classRoot.setFlag(Flags.Enum)
moduleRoot.setFlag(Flags.Enum)
}

result
}

Expand Down
3 changes: 2 additions & 1 deletion src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
Bind(defaultSym, Underscore(selector.tpe.widen)),
EmptyTree,
Literal(Constant(false)))
cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase)
val annotated = Annotated(New(ref(defn.UncheckedAnnotType)), paramRef)
cpy.Match(applyRhs)(annotated, cases.map(translateCase) :+ defaultCase)
case _ =>
tru
}
Expand Down
18 changes: 10 additions & 8 deletions src/dotty/tools/dotc/transform/PatternMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Applications._
import TypeApplications._
import SymUtils._, core.NameOps._
import core.Mode
import patmat._

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

// check exhaustivity and unreachability
val engine = new SpaceEngine
if (engine.checkable(tree)) {
engine.checkExhaustivity(tree)
engine.checkRedundancy(tree)
}

translated.ensureConforms(tree.tpe)
}

Expand Down Expand Up @@ -1244,13 +1252,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
case _ => false
}

def elimAnonymousClass(t: Type) = t match {
case t:TypeRef if t.symbol.isAnonymousClass =>
t.symbol.asClass.typeRef.asSeenFrom(t.prefix, t.symbol.owner)
case _ =>
t
}

/** Implement a pattern match by turning its cases (including the implicit failure case)
* into the corresponding (monadic) extractors, and combining them with the `orElse` combinator.
*
Expand All @@ -1264,7 +1265,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
def translateMatch(match_ : Match): Tree = {
val Match(sel, cases) = match_

val selectorTp = elimAnonymousClass(sel.tpe.widen/*withoutAnnotations*/)
val selectorTp = sel.tpe.widen.deAnonymize/*withoutAnnotations*/

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

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


// checkMatchVariablePatterns(nonSyntheticCases) // only used for warnings

// we don't transform after uncurry
Expand Down
9 changes: 9 additions & 0 deletions src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import Symbols._, TypeUtils._
*
* (9) Adds SourceFile annotations to all top-level classes and objects
*
* (10) Adds Child annotations to all sealed classes
*
* The reason for making this a macro transform is that some functions (in particular
* super and protected accessors and instantiation checks) are naturally top-down and
* don't lend themselves to the bottom-up approach of a mini phase. The other two functions
Expand Down Expand Up @@ -243,6 +245,13 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
ctx.compilationUnit.source.exists &&
sym != defn.SourceFileAnnot)
sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path))

if (!sym.isAnonymousClass) // ignore anonymous class
for (parent <- sym.asClass.classInfo.classParents) {
val pclazz = parent.classSymbol
if (pclazz.is(Sealed)) pclazz.addAnnotation(Annotation.makeChild(sym))
}

tree
}
else {
Expand Down
Loading