From 5cdd42f3fd4cb6faa8670b53ead6a1e7fce062f1 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Wed, 11 May 2016 10:37:18 +0200 Subject: [PATCH 01/11] initial works --- .../tools/dotc/transform/PatternMatcher.scala | 10 + .../tools/dotc/transform/PostTyper.scala | 9 + src/dotty/tools/dotc/transform/Space.scala | 483 +++++++++++ .../transform/PatmatExhaustivityTest.scala | 58 ++ tests/patmat/patmat-adt.check | 21 + tests/patmat/patmat-adt.scala | 53 ++ tests/patmat/patmat-extractor.scala | 17 + tests/patmat/patmat-ortype.check | 13 + tests/patmat/patmat-ortype.scala | 40 + tests/patmat/patmatexhaust-huge.check | 5 + tests/patmat/patmatexhaust-huge.scala | 806 ++++++++++++++++++ tests/patmat/patmatexhaust.check | 33 + tests/patmat/patmatexhaust.scala | 131 +++ tests/patmat/t3097.scala | 35 + tests/patmat/t3111.check | 8 + tests/patmat/t3111.scala | 13 + tests/patmat/t3163.check | 5 + tests/patmat/t3163.scala | 3 + tests/patmat/t3683.scala | 19 + tests/patmat/t4020.scala | 25 + tests/patmat/t4333.scala.ignore | 7 + tests/patmat/t4408.check | 5 + tests/patmat/t4408.scala | 16 + tests/patmat/t4526.check | 13 + tests/patmat/t4526.scala | 16 + tests/patmat/t4691.check | 5 + tests/patmat/t4691.scala | 18 + tests/patmat/t5440.check | 5 + tests/patmat/t5968.scala | 7 + tests/patmat/t6008.scala | 5 + tests/patmat/t6420.check | 5 + tests/patmat/t6420.scala | 11 + tests/patmat/t6450.scala | 9 + tests/patmat/t6818.scala | 11 + tests/patmat/t7206.scala.ignore | 19 + tests/patmat/t7285.check | 13 + tests/patmat/t7285.scala | 55 ++ tests/patmat/t7285a.scala | 83 ++ tests/patmat/t7298.scala | 11 + tests/patmat/t7353.scala | 11 + tests/patmat/t7437.scala | 17 + tests/patmat/t7466.check | 5 + tests/patmat/t7466.scala | 17 + tests/patmat/t7631.check | 5 + tests/patmat/t7631.scala | 11 + tests/patmat/t7669.check | 5 + tests/patmat/t7669.scala | 14 + tests/patmat/t7746.check | 5 + tests/patmat/t7746.scala | 5 + tests/patmat/t8068.scala | 14 + tests/patmat/t8178.check | 13 + tests/patmat/t8178.scala | 33 + tests/patmat/t8412.check | 5 + tests/patmat/t8412.scala | 14 + tests/patmat/t8430.check | 5 + tests/patmat/t8430.scala | 19 + tests/patmat/t8511.check | 5 + tests/patmat/t8511.scala | 25 + tests/patmat/t8546.scala | 49 ++ tests/patmat/t8606.scala | 18 + tests/patmat/t9129.check | 5 + tests/patmat/t9129.scala | 29 + tests/patmat/t9232.check | 5 + tests/patmat/t9232.scala | 16 + tests/patmat/t9289.check | 9 + tests/patmat/t9289.scala | 28 + tests/patmat/t9351.check | 13 + tests/patmat/t9351.scala | 35 + tests/patmat/t9398.check | 5 + tests/patmat/t9398.scala | 13 + tests/patmat/t9399.scala | 16 + tests/patmat/t9573.check | 5 + tests/patmat/t9573.scala | 13 + tests/patmat/t9630.scala | 21 + tests/patmat/t9657.scala.ignore | 28 + tests/patmat/t9672.check | 5 + tests/patmat/t9672.scala | 28 + tests/patmat/t9677.scala | 23 + 78 files changed, 2693 insertions(+) create mode 100644 src/dotty/tools/dotc/transform/Space.scala create mode 100644 test/test/transform/PatmatExhaustivityTest.scala create mode 100644 tests/patmat/patmat-adt.check create mode 100644 tests/patmat/patmat-adt.scala create mode 100644 tests/patmat/patmat-extractor.scala create mode 100644 tests/patmat/patmat-ortype.check create mode 100644 tests/patmat/patmat-ortype.scala create mode 100644 tests/patmat/patmatexhaust-huge.check create mode 100644 tests/patmat/patmatexhaust-huge.scala create mode 100644 tests/patmat/patmatexhaust.check create mode 100644 tests/patmat/patmatexhaust.scala create mode 100644 tests/patmat/t3097.scala create mode 100644 tests/patmat/t3111.check create mode 100644 tests/patmat/t3111.scala create mode 100644 tests/patmat/t3163.check create mode 100644 tests/patmat/t3163.scala create mode 100644 tests/patmat/t3683.scala create mode 100644 tests/patmat/t4020.scala create mode 100644 tests/patmat/t4333.scala.ignore create mode 100644 tests/patmat/t4408.check create mode 100644 tests/patmat/t4408.scala create mode 100644 tests/patmat/t4526.check create mode 100644 tests/patmat/t4526.scala create mode 100644 tests/patmat/t4691.check create mode 100644 tests/patmat/t4691.scala create mode 100644 tests/patmat/t5440.check create mode 100644 tests/patmat/t5968.scala create mode 100644 tests/patmat/t6008.scala create mode 100644 tests/patmat/t6420.check create mode 100644 tests/patmat/t6420.scala create mode 100644 tests/patmat/t6450.scala create mode 100644 tests/patmat/t6818.scala create mode 100644 tests/patmat/t7206.scala.ignore create mode 100644 tests/patmat/t7285.check create mode 100644 tests/patmat/t7285.scala create mode 100644 tests/patmat/t7285a.scala create mode 100644 tests/patmat/t7298.scala create mode 100644 tests/patmat/t7353.scala create mode 100644 tests/patmat/t7437.scala create mode 100644 tests/patmat/t7466.check create mode 100644 tests/patmat/t7466.scala create mode 100644 tests/patmat/t7631.check create mode 100644 tests/patmat/t7631.scala create mode 100644 tests/patmat/t7669.check create mode 100644 tests/patmat/t7669.scala create mode 100644 tests/patmat/t7746.check create mode 100644 tests/patmat/t7746.scala create mode 100644 tests/patmat/t8068.scala create mode 100644 tests/patmat/t8178.check create mode 100644 tests/patmat/t8178.scala create mode 100644 tests/patmat/t8412.check create mode 100644 tests/patmat/t8412.scala create mode 100644 tests/patmat/t8430.check create mode 100644 tests/patmat/t8430.scala create mode 100644 tests/patmat/t8511.check create mode 100644 tests/patmat/t8511.scala create mode 100644 tests/patmat/t8546.scala create mode 100644 tests/patmat/t8606.scala create mode 100644 tests/patmat/t9129.check create mode 100644 tests/patmat/t9129.scala create mode 100644 tests/patmat/t9232.check create mode 100644 tests/patmat/t9232.scala create mode 100644 tests/patmat/t9289.check create mode 100644 tests/patmat/t9289.scala create mode 100644 tests/patmat/t9351.check create mode 100644 tests/patmat/t9351.scala create mode 100644 tests/patmat/t9398.check create mode 100644 tests/patmat/t9398.scala create mode 100644 tests/patmat/t9399.scala create mode 100644 tests/patmat/t9573.check create mode 100644 tests/patmat/t9573.scala create mode 100644 tests/patmat/t9630.scala create mode 100644 tests/patmat/t9657.scala.ignore create mode 100644 tests/patmat/t9672.check create mode 100644 tests/patmat/t9672.scala create mode 100644 tests/patmat/t9677.scala diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 974053769233..9e3047645a34 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -50,8 +50,17 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans private var _id = 0 // left for debuging override def transformMatch(tree: Match)(implicit ctx: Context, info: TransformerInfo): Tree = { + val Match(sel, cases) = tree + val translated = new Translator()(ctx).translator.translateMatch(tree) + // check exhaustivity and unreachability + val engine = new SpaceEngine + if (!engine.skipCheck(sel.tpe)) { + engine.exhaustivity(tree) + engine.redundancy(tree) + } + translated.ensureConforms(tree.tpe) } @@ -1275,6 +1284,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans case _ => (cases, None) } + // checkMatchVariablePatterns(nonSyntheticCases) // only used for warnings // we don't transform after uncurry diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index fcde59b24a03..4bfb79726aa6 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -38,6 +38,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 @@ -231,6 +233,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 { diff --git a/src/dotty/tools/dotc/transform/Space.scala b/src/dotty/tools/dotc/transform/Space.scala new file mode 100644 index 000000000000..30a297d0c6bc --- /dev/null +++ b/src/dotty/tools/dotc/transform/Space.scala @@ -0,0 +1,483 @@ +package dotty.tools.dotc +package transform + +import core.Types._ +import core.Contexts._ +import core.Flags._ +import ast.Trees._ +import ast.tpd +import core.Decorators._ +import core.TypeApplications._ +import core.Symbols._ +import core.NameOps._ +import core.Constants._ + +/** Space logic for checking exhaustivity and unreachability of + * pattern matching. + */ + + +/** space definition */ +sealed trait Space +case object Empty extends Space +case class Typ(tp: Type, decomposed: Boolean) extends Space +case class Kon(tp: Type, params: List[Space]) extends Space +sealed trait Point extends Space +case class Var(sym: Symbol, tp: Type) extends Point +case class Const(value: Constant, tp: Type) extends Point +case class Or(spaces: List[Space]) extends Space + +/** abstract space logic */ +trait SpaceLogic { + /** Is `tp1` a subtype of `tp2`? */ + def isSubType(tp1: Type, tp2: Type): Boolean + + /** Is `tp1` the same type as `tp2`? */ + def isEqualType(tp1: Type, tp2: Type): Boolean + + /** Is `tp` a case class? */ + def isCaseClass(tp: Type): Boolean + + /** Is the type `tp` decomposable? i.e. all values of the type can be covered + * by its decomposed types. + * + * Abstract sealed class, OrType and Boolean can be decomposed. + */ + def canDecompose(tp: Type): Boolean + + /** Return parameters types of the case class `tp` */ + def signature(tp: Type): List[Type] + + /** Get components of decomposable types */ + def partitions(tp: Type): List[Space] + + /** Simplify space using the laws, there's no nested union after simplify */ + def simplify(space: Space): Space = space match { + case Kon(tp, spaces) => + val sp = Kon(tp, spaces.map(simplify _)) + if (sp.params.exists(_ == Empty)) Empty + else sp + case Or(spaces) => + val set = spaces.map(simplify _).flatMap { + case Or(ss) => ss + case s => Seq(s) + } filter (_ != Empty) + + if (set.isEmpty) Empty + else if (set.size == 1) set.toList(0) + else Or(set) + case Typ(tp, _) => + if (canDecompose(tp) && partitions(tp).isEmpty) Empty + else space + case _ => space + } + + /** Flatten space to get rid of `Or` for pretty print */ + def flatten(space: Space): List[Space] = space match { + case Kon(tp, spaces) => + val flats = spaces.map(flatten _) + + flats.foldLeft(List[Kon]()) { (acc, flat) => + if (acc.isEmpty) flat.map(s => Kon(tp, Nil :+ s)) + else for (Kon(tp, ss) <- acc; s <- flat) yield Kon(tp, ss :+ s) + } + case Or(spaces) => + spaces.flatMap(flatten _) + case _ => List(space) + } + + /** Is `a` a subspace of `b`? Equivalent to `a - b == Empty`, but faster */ + def subspace(a: Space, b: Space): Boolean = (a, b) match { + case (Empty, _) => true + case (_, Empty) => false + case (Or(ss), _) => ss.forall(subspace(_, b)) + case (Typ(tp1, _), Typ(tp2, _)) => + isSubType(tp1, tp2) + case (Typ(tp1, _), Or(ss)) => + ss.exists(subspace(a, _)) || + (canDecompose(tp1) && subspace(Or(partitions(tp1)), b)) + case (Typ(tp1, _), Kon(tp2, ss)) => + isSubType(tp1, tp2) && subspace(Kon(tp2, signature(tp2).map(tp => Typ(tp, false))), b) + case (Kon(tp1, ss), Typ(tp2, _)) => + isSubType(tp1, tp2) || + simplify(a) == Empty || + (isSubType(tp2, tp1) && + canDecompose(tp1) && + subspace(Or(partitions(tp1)), b)) + case (Kon(_, _), Or(_)) => + simplify(minus(a, b)) == Empty + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + isEqualType(tp1, tp2) && ss1.zip(ss2).forall((subspace _).tupled) + case (Const(v1, _), Const(v2, _)) => v1 == v2 + case (Const(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) + case (Const(_, _), Or(ss)) => ss.exists(subspace(a, _)) + case (Const(_, _), _) => false + case (_, Const(_, _)) => false + case (Var(x, _), Var(y, _)) => x == y + case (Var(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) + case (Var(_, _), Or(ss)) => ss.exists(subspace(a, _)) + case (Var(_, _), _) => false + case (_, Var(_, _)) => false + } + + /** Intersection of two spaces */ + def intersect(a: Space, b: Space): Space = (a, b) match { + case (Empty, _) | (_, Empty) => Empty + case (_, Or(ss)) => Or(ss.map(intersect(a, _))) + case (Or(ss), _) => Or(ss.map(intersect(_, b))) + case (Typ(tp1, _), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (isSubType(tp2, tp1)) b + else Empty + case (Typ(tp1, _), Kon(tp2, ss)) => + if (isSubType(tp2, tp1)) b + else if (isSubType(tp1, tp2)) a + else Empty + case (Kon(tp1, ss), Typ(tp2, _)) => + if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) a + else Empty + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + if (!isEqualType(tp1, tp2)) Empty + else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) Empty + else + Kon(tp1, ss1.zip(ss2).map((intersect _).tupled)) + case (Const(v1, _), Const(v2, _)) => + if (v1 == v2) a else Empty + case (Const(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a else Empty + case (Const(_, _), _) => Empty + case (Typ(tp1, _), Const(_, tp2)) => + if (isSubType(tp2, tp1)) b else Empty + case (_, Const(_, _)) => Empty + case (Var(x, _), Var(y, _)) => + if (x == y) a else Empty + case (Var(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a else Empty + case (Var(_, _), _) => Empty + case (Typ(tp1, _), Var(_, tp2)) => + if (isSubType(tp2, tp1)) b else Empty + case (_, Var(_, _)) => Empty + } + + /** The space of a not covered by b */ + def minus(a: Space, b: Space): Space = (a, b) match { + case (Empty, _) => Empty + case (_, Empty) => a + case (Typ(tp1, _), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (isSubType(tp2, tp1) && canDecompose(tp1)) + minus(Or(partitions(tp1)), b) + else a + case (Typ(tp1, _), Kon(tp2, ss)) => + if (isSubType(tp1, tp2)) minus(Kon(tp2, signature(tp2).map(tp => Typ(tp, false))), b) + else if (isSubType(tp2, tp1) && canDecompose(tp1)) + minus(Or(partitions(tp1)), b) + else a + case (_, Or(ss)) => + ss.foldLeft(a)(minus) + case (Or(ss), _) => + Or(ss.map(minus(_, b))) + case (Kon(tp1, ss), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (simplify(a) == Empty) Empty + else if (isSubType(tp2, tp1) && canDecompose(tp1)) + minus(Or(partitions(tp1)), b) + else a + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + if (!isEqualType(tp1, tp2)) a + else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) a + else if (ss1.zip(ss2).forall((subspace _).tupled)) Empty + else + Or( + ss1.zip(ss2).map((minus _).tupled).zip(0 to ss2.length - 1).map { + case (ri, i) => Kon(tp1, ss1.updated(i, ri)) + }) + case (Const(v1, _), Const(v2, _)) => + if (v1 == v2) Empty else a + case (Const(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty else a + case (Const(_, _), _) => a + case (Typ(tp1, _), Const(_, tp2)) => // Boolean + if (isSubType(tp2, tp1) && canDecompose(tp1)) + minus(Or(partitions(tp1)), b) + else a + case (_, Const(_, _)) => a + case (Var(x, _), Var(y, _)) => + if (x == y) Empty else a + case (Var(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty else a + case (Var(_, _), _) => a + case (_, Var(_, _)) => a + } +} + +/** Scala implementation of space logic */ +class SpaceEngine(implicit ctx: Context) extends SpaceLogic { + import tpd._ + + def debug(s: String): Unit = { + if (ctx.debug) println(s) + } + + /** Return the space that represents the pattern `pat` + * + * If roundUp is true, approximate extractors to its type, + * otherwise approximate extractors to Empty + */ + def project(pat: Tree, roundUp: Boolean = true)(implicit ctx: Context): Space = pat match { + case Literal(c) => Const(c, c.tpe) + case _: BackquotedIdent => Var(pat.symbol, pat.tpe) + case Ident(_) => Typ(pat.tpe.stripAnnots, false) + case Select(_, _) => + if (pat.symbol.is(Module)) + Typ(pat.tpe.stripAnnots, false) + else + Var(pat.symbol, pat.tpe) + case Alternative(trees) => Or(trees.map(project(_, roundUp))) + case Bind(_, pat) => project(pat) + case UnApply(_, _, pats) => + if (pat.tpe.classSymbol.is(CaseClass)) + Kon(pat.tpe.stripAnnots, pats.map(pat => project(pat, roundUp))) + else if (roundUp) Typ(pat.tpe, false) + else Empty + case Typed(pat @ UnApply(_, _, _), _) => project(pat) + case Typed(expr, _) => Typ(expr.tpe.stripAnnots, true) + case _ => + debug(s"========unkown tree: $pat========") + Empty + } + + /** Is `tp1` a subtype of `tp2`? + * + * Ignore type parameters in comparison due to erasure, i.e., Some[Int] <: Some[T] + */ + def isSubType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match { + case (tp1: RefinedType, tp2: RefinedType) => isSubType(tp1.parent, tp2.parent) + case (tp1: RefinedType, _) => isSubType(tp1.parent, tp2) + case (_, tp2: RefinedType) => isSubType(tp1, tp2.parent) + case (_, _) => + val res = tp1 <:< tp2 + debug(s"$tp1 <: $tp2 ? $res") + res + } + + def isEqualType(tp1: Type, tp2: Type): Boolean = { + val res = tp1 =:= tp2 + debug(s"$tp1 == $tp2 ? $res") + res + } + + def signature(tp: Type): List[Type] = { + val ktor = tp.classSymbol.primaryConstructor.info + + debug(s"=======ktor: $ktor") + + val meth = + if (ktor.isInstanceOf[MethodType]) ktor + else + tp match { + case AppliedType(_, params) => + debug(s"=======params: $params") + val refined = params.map { + // TypeBounds would generate an exception + case tp: TypeBounds => tp.underlying + case tp => tp + } + debug(s"=======refined params: $refined") + ktor.appliedTo(refined) + case _ => + ktor + } + + val sign = meth.firstParamTypes.map(_.stripTypeVar).map(paramTp => refine(tp, paramTp)) + + debug(s"====signature of $tp: $sign") + sign + } + + def partitions(tp: Type): List[Space] = tp match { + case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true)) + case _ if tp =:= ctx.definitions.BooleanType => + List( + Const(Constant(true), ctx.definitions.BooleanType), + Const(Constant(false), ctx.definitions.BooleanType) + ) + case _ => + val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot => + // refer to definition of Annotation.makeChild + val sym = annot.tree match { + case Apply(TypeApply(_, List(tpTree)), _) => tpTree.symbol.asClass + } + + if (sym.is(ModuleClass)) + sym.classInfo.selfType + else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef]) + refine(tp, sym.classInfo.symbolicTypeRef) + else + sym.info + } filter(_ <:< tp) // child may not always be subtype of parent: SI-4020 + + debug(s"=========child of ${tp.show}: ${children.map(_.show).mkString(", ")}") + + children.map(tp => Typ(tp, true)) + } + + /** Refine tp2 based on tp1 + * + * E.g. if `tp1` is `Option[Int]`, `tp2` is `Some`, then return + * `Some[Int]`. + * + * If `tp1` is `path1.A`, `tp2` is `path2.B`, and `path1` is subtype of + * `path2`, then return `path1.B`. + */ + def refine(tp1: Type, tp2: Type): Type = (tp1, tp2) match { + case (tp1: RefinedType, _) => tp1.wrapIfMember(refine(tp1.parent, tp2)) + case (TypeRef(ref1: TypeProxy, _), tp2 @ TypeRef(ref2: TypeProxy, name)) => + if (ref1.underlying <:< ref2.underlying) TypeRef(ref1, name) else tp2 + case _ => tp2 + } + + /** Abstract sealed types, or-types and Boolean can be decomposed */ + def canDecompose(tp: Type): Boolean = { + tp.typeSymbol.is(allOf(Abstract, Sealed)) || + tp.typeSymbol.is(allOf(Trait, Sealed)) || + tp.isInstanceOf[OrType] || + tp =:= ctx.definitions.BooleanType + } + + def isCaseClass(tp: Type): Boolean = tp.classSymbol.isClass && tp.classSymbol.is(CaseClass) + + /** Show friendly type name with current scope in mind + * + * E.g. C.this.B --> B if current owner is C + * C.this.x.T --> x.T if current owner is C + * X[T] --> X + * C --> C if current owner is C !!! + * + */ + def showType(tp: Type): String = { + val enclosingCls = ctx.owner.enclosingClass.asClass.classInfo.symbolicTypeRef + + def isOmittable(sym: Symbol) = + sym.isEffectiveRoot || sym.isAnonymousClass || sym.name.isReplWrapperName || + ctx.definitions.UnqualifiedOwnerTypes.exists(_.symbol == sym) || + sym.showFullName.startsWith("scala.") || + sym == enclosingCls.typeSymbol + + def refinePrefix(tp: Type): String = tp match { + case NoPrefix => "" + case tp: NamedType if isOmittable(tp.symbol) => "" + case tp: ThisType => refinePrefix(tp.tref) + case tp: RefinedType => refinePrefix(tp.parent) + case tp: NamedType => tp.name.show.stripSuffix("$") + } + + def refine(tp: Type): String = tp match { + case tp: RefinedType => refine(tp.parent) + case tp: ThisType => refine(tp.tref) + case tp: NamedType => + val pre = refinePrefix(tp.prefix) + if (pre.isEmpty) tp.name.show.stripSuffix("$") + else pre + "." + tp.name.show.stripSuffix("$") + case _ => tp.show.stripSuffix("$") + } + + val text = tp match { + case tp: OrType => showType(tp.tp1) + " | " + showType(tp.tp2) + case _ => refine(tp) + } + + if (text.isEmpty) enclosingCls.show.stripSuffix("$") + else text + } + + /** Display spaces */ + def show(s: Space): String = { + def doShow(s: Space, mergeList: Boolean = false): String = s match { + case Empty => "" + case Const(v, _) => v.show + case Var(x, _) => x.show + case Typ(tp, decomposed) => + val sym = tp.widen.classSymbol + + if (sym.is(ModuleClass)) + showType(tp) + else if (ctx.definitions.isTupleType(tp)) + signature(tp).map(_ => "_").mkString("(", ", ", ")") + else if (sym.showFullName == "scala.collection.immutable.::") + if (mergeList) "_" else "List(_)" + else if (tp.classSymbol.is(CaseClass)) + // use constructor syntax for case class + showType(tp) + signature(tp).map(_ => "_").mkString("(", ",", ")") + else if (signature(tp).nonEmpty) + tp.classSymbol.name + signature(tp).map(_ => "_").mkString("(", ",", ")") + else if (decomposed) "_: " + showType(tp) + else "_" + case Kon(tp, params) => + if (ctx.definitions.isTupleType(tp)) + "(" + params.map(p => doShow(p)).mkString(", ") + ")" + else if (tp.widen.classSymbol.showFullName == "scala.collection.immutable.::") + if (mergeList) params.map(p => doShow(p, mergeList)).mkString(", ") + else params.map(p => doShow(p, true)).mkString("List(", ", ", ")") + else + showType(tp) + params.map(p => doShow(p)).mkString("(", ", ", ")") + case Or(_) => + throw new Exception("incorrect flatten result " + s) + } + + flatten(s).map(doShow(_, false)).distinct.mkString(", ") + } + + /** Does the type t have @unchecked annotation? */ + def skipCheck(tp: Type): Boolean = tp match { + case AnnotatedType(tp, annot) => ctx.definitions.UncheckedAnnot == annot.symbol + case _ => false + } + + /** Widen a type and eliminate anonymous classes */ + private def widen(tp: Type): Type = tp.widen match { + case tp:TypeRef if tp.symbol.isAnonymousClass => + tp.symbol.asClass.typeRef.asSeenFrom(tp.prefix, tp.symbol.owner) + case tp => tp + } + + def exhaustivity(_match: Match): Unit = { + val Match(sel, cases) = _match + val selTyp = widen(sel.tpe) + + debug(s"====patterns:\n${cases.map(_.pat).mkString("\n")}") + val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or(List(a, b))) + debug(s"====selector:\n" + selTyp) + debug("====pattern space:\n" + show(patternSpace)) + val uncovered = simplify(minus(Typ(selTyp, true), patternSpace)) + + if (uncovered != Empty) { + ctx.warning( + "match may not be exhaustive.\n" + + s"It would fail on the following input: " + + show(uncovered), _match.pos + ) + } + } + + def redundancy(_match: Match): Unit = { + val Match(sel, cases) = _match + val selTyp = widen(sel.tpe) + + // starts from the second, the first can't be redundant + (1 until cases.length).foreach { i => + // in redundancy check, take guard as false, take extractor as match + // nothing in order to soundly approximate + val prevs = cases.take(i).map { x => + if (x.guard.isEmpty) project(x.pat, false) + else Empty + }.reduce((a, b) => Or(List(a, b))) + + val curr = project(cases(i).pat) + + if (subspace(curr, prevs)) { + ctx.warning("unreachable code", cases(i).body.pos) + } + } + } +} diff --git a/test/test/transform/PatmatExhaustivityTest.scala b/test/test/transform/PatmatExhaustivityTest.scala new file mode 100644 index 000000000000..1ecaa2b1d14d --- /dev/null +++ b/test/test/transform/PatmatExhaustivityTest.scala @@ -0,0 +1,58 @@ +package test.transform + +import java.io._ +import scala.io.Source._ +import scala.reflect.io.Directory +import org.junit.Test + +import dotty.tools.dotc.Main +import dotty.tools.dotc.reporting.ConsoleReporter + +class PatmatExhaustivityTest { + val testsDir = "./tests/patmat" + val options = "-Ystop-after:splitter" // no need for code generation + // patmatexhaust-huge.scala crash compiler + + private def compileFile(file: File) = { + val stringBuffer = new StringWriter() + val reporter = new ConsoleReporter(writer = new PrintWriter(stringBuffer)) + + try { + Main.process(Array(file.getPath, options), reporter, null) + } catch { + case e: Throwable => + println(s"Compile $file exception:") + e.printStackTrace() + } + + val actual = stringBuffer.toString.trim + val checkFilePath = file.getAbsolutePath.stripSuffix(".scala") + ".check" + val checkContent = + if (new File(checkFilePath).exists) + fromFile(checkFilePath).getLines.mkString("\n").trim + else "" + + (file, checkContent, actual) + } + + @Test def patmatExhaustivity: Unit = { + val res = Directory(testsDir).deepFiles.toList.filter(_.extension == "scala").map { f => + compileFile(f.jfile) + } + + val failed = res.filter { case (_, expected, actual) => expected != actual } + val ignored = Directory(testsDir).deepFiles.toList.filter(_.extension == "ignore") + + failed.foreach { case (file, expected, actual) => + println(s"\n----------------- incorrect output for $file --------------\n" + + s"Expected:\n-------\n$expected\n\nActual\n----------\n$actual\n" + ) + } + + val msg = s"Total: ${res.length + ignored.length}, Failed: ${failed.length}, Ignored: ${ignored.length}" + + assert(failed.length == 0, msg) + + println(msg) + } +} diff --git a/tests/patmat/patmat-adt.check b/tests/patmat/patmat-adt.check new file mode 100644 index 000000000000..f4e1ce369e50 --- /dev/null +++ b/tests/patmat/patmat-adt.check @@ -0,0 +1,21 @@ +./tests/patmat/patmat-adt.scala:7: warning: match may not be exhaustive. +It would fail on the following input: Bad(Good(_)), Good(Bad(_)) + def foo1a(x: Odd) = x match { // warning: Good(_: Bad), Bad(_: Good) + ^ +./tests/patmat/patmat-adt.scala:19: warning: match may not be exhaustive. +It would fail on the following input: Some(_) + def foo2(x: Option[Int]) = x match { // warning: Some(_: Int) + ^ +./tests/patmat/patmat-adt.scala:24: warning: match may not be exhaustive. +It would fail on the following input: (None, Some(_)), (_, Some(_)) + def foo3a[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, Some(_)) + ^ +./tests/patmat/patmat-adt.scala:29: warning: match may not be exhaustive. +It would fail on the following input: (None, None), (Some(_), Some(_)) + def foo3b[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, None) + ^ +./tests/patmat/patmat-adt.scala:50: warning: match may not be exhaustive. +It would fail on the following input: LetL(BooleanLit), LetL(IntLit) + def foo5(tree: Tree) : Any = tree match { + ^ +5 warnings found \ No newline at end of file diff --git a/tests/patmat/patmat-adt.scala b/tests/patmat/patmat-adt.scala new file mode 100644 index 000000000000..acb4d3465ee6 --- /dev/null +++ b/tests/patmat/patmat-adt.scala @@ -0,0 +1,53 @@ +object PatmatADT { + abstract sealed class Odd(x: Odd) + + case class Good(x: Odd) extends Odd(x) + case class Bad(x: Odd) extends Odd(x) + + def foo1a(x: Odd) = x match { // warning: Good(_: Bad), Bad(_: Good) + case Good(_: Good) => false + case Bad(_: Bad) => false + } + + def foo1b(x: Odd) = x match { + case Good(_: Good) => false + case Bad(_: Bad) => false + case Good(_: Bad) => false + case Bad(_: Good) => false + } + + def foo2(x: Option[Int]) = x match { // warning: Some(_: Int) + case Some(_: Double) => true + case None => true + } + + def foo3a[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, Some(_)) + case (Some(_), None) => true + case (None, None) => true + } + + def foo3b[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, None) + case (Some(_), None) => true + case (None, Some(_)) => true + } + + sealed trait Base + case class Foo() extends Base + + def foo4(x: Base) = x match { + case Foo() => + } + + sealed abstract class CL3Literal + case object IntLit extends CL3Literal + case object CharLit extends CL3Literal + case object BooleanLit extends CL3Literal + + + sealed abstract class Tree + case class LetL(value: CL3Literal) extends Tree + + def foo5(tree: Tree) : Any = tree match { + case LetL(CharLit) => + } +} \ No newline at end of file diff --git a/tests/patmat/patmat-extractor.scala b/tests/patmat/patmat-extractor.scala new file mode 100644 index 000000000000..02fde96dcb42 --- /dev/null +++ b/tests/patmat/patmat-extractor.scala @@ -0,0 +1,17 @@ +sealed trait Node +case class NodeA(i: Int) extends Node +case class NodeB(b: Boolean) extends Node +case class NodeC(s: String) extends Node + +object Node { + def unapply(node: Node): Option[(Node, Node)] = ??? +} + +// currently scalac can't do anything with following +// it's possible to do better in our case +object Test { + def foo(x: Node): Boolean = x match { // unexhaustive + case Node(NodeA(_), NodeB(_)) => true + case Node(NodeA(4), NodeB(false)) => true // unreachable code + } +} \ No newline at end of file diff --git a/tests/patmat/patmat-ortype.check b/tests/patmat/patmat-ortype.check new file mode 100644 index 000000000000..2291da251994 --- /dev/null +++ b/tests/patmat/patmat-ortype.check @@ -0,0 +1,13 @@ +./tests/patmat/patmat-ortype.scala:8: warning: match may not be exhaustive. +It would fail on the following input: _: String + def foo2a(x: Int | Double | String) = x match { // _: String not matched + ^ +./tests/patmat/patmat-ortype.scala:18: warning: match may not be exhaustive. +It would fail on the following input: Some(_: String), None + def foo3(x: Option[Int | Double | String]) = x match { // warning: None, Some(_: String) not matched + ^ +./tests/patmat/patmat-ortype.scala:36: warning: match may not be exhaustive. +It would fail on the following input: Some(_: String) + def foo5b(x: Option[Int | Double | String]) = x match { // warning: Some(_: String) not matched + ^ +three warnings found \ No newline at end of file diff --git a/tests/patmat/patmat-ortype.scala b/tests/patmat/patmat-ortype.scala new file mode 100644 index 000000000000..c7419acd3b0f --- /dev/null +++ b/tests/patmat/patmat-ortype.scala @@ -0,0 +1,40 @@ +object PatmatOrType { + + def foo1(x: Int | Double) = x match { + case _: Int => true + case _: Double => true + } + + def foo2a(x: Int | Double | String) = x match { // _: String not matched + case _: Int => true + case _: Double => true + } + + def foo2b(x: Int | Double | String) = x match { + case _: Int => true + case _: (Double | String) => true + } + + def foo3(x: Option[Int | Double | String]) = x match { // warning: None, Some(_: String) not matched + case Some(_: Int) => true + case Some(_: Double) => true + } + + def foo4(x: Option[Int | Double | String]) = x match { + case Some(_: Int) => true + case Some(_: Double) => true + case Some(_: String) => true + case None => false + } + + def foo5a(x: Option[Int | Double | String]) = x match { + case Some(_: (Int | Double)) => true + case Some(_: String) => true + case None => false + } + + def foo5b(x: Option[Int | Double | String]) = x match { // warning: Some(_: String) not matched + case Some(_: (Int | Double)) => true + case None => false + } +} \ No newline at end of file diff --git a/tests/patmat/patmatexhaust-huge.check b/tests/patmat/patmatexhaust-huge.check new file mode 100644 index 000000000000..06cac90bd5e7 --- /dev/null +++ b/tests/patmat/patmatexhaust-huge.check @@ -0,0 +1,5 @@ +./tests/patmat/patmatexhaust-huge.scala:404: warning: match may not be exhaustive. +It would fail on the following input: C397, C392 + def f(c: C): Int = c match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/patmatexhaust-huge.scala b/tests/patmat/patmatexhaust-huge.scala new file mode 100644 index 000000000000..c4008b99542a --- /dev/null +++ b/tests/patmat/patmatexhaust-huge.scala @@ -0,0 +1,806 @@ +abstract sealed trait C +case object C1 extends C +case object C2 extends C +case object C3 extends C +case object C4 extends C +case object C5 extends C +case object C6 extends C +case object C7 extends C +case object C8 extends C +case object C9 extends C +case object C10 extends C +case object C11 extends C +case object C12 extends C +case object C13 extends C +case object C14 extends C +case object C15 extends C +case object C16 extends C +case object C17 extends C +case object C18 extends C +case object C19 extends C +case object C20 extends C +case object C21 extends C +case object C22 extends C +case object C23 extends C +case object C24 extends C +case object C25 extends C +case object C26 extends C +case object C27 extends C +case object C28 extends C +case object C29 extends C +case object C30 extends C +case object C31 extends C +case object C32 extends C +case object C33 extends C +case object C34 extends C +case object C35 extends C +case object C36 extends C +case object C37 extends C +case object C38 extends C +case object C39 extends C +case object C40 extends C +case object C41 extends C +case object C42 extends C +case object C43 extends C +case object C44 extends C +case object C45 extends C +case object C46 extends C +case object C47 extends C +case object C48 extends C +case object C49 extends C +case object C50 extends C +case object C51 extends C +case object C52 extends C +case object C53 extends C +case object C54 extends C +case object C55 extends C +case object C56 extends C +case object C57 extends C +case object C58 extends C +case object C59 extends C +case object C60 extends C +case object C61 extends C +case object C62 extends C +case object C63 extends C +case object C64 extends C +case object C65 extends C +case object C66 extends C +case object C67 extends C +case object C68 extends C +case object C69 extends C +case object C70 extends C +case object C71 extends C +case object C72 extends C +case object C73 extends C +case object C74 extends C +case object C75 extends C +case object C76 extends C +case object C77 extends C +case object C78 extends C +case object C79 extends C +case object C80 extends C +case object C81 extends C +case object C82 extends C +case object C83 extends C +case object C84 extends C +case object C85 extends C +case object C86 extends C +case object C87 extends C +case object C88 extends C +case object C89 extends C +case object C90 extends C +case object C91 extends C +case object C92 extends C +case object C93 extends C +case object C94 extends C +case object C95 extends C +case object C96 extends C +case object C97 extends C +case object C98 extends C +case object C99 extends C +case object C100 extends C +case object C101 extends C +case object C102 extends C +case object C103 extends C +case object C104 extends C +case object C105 extends C +case object C106 extends C +case object C107 extends C +case object C108 extends C +case object C109 extends C +case object C110 extends C +case object C111 extends C +case object C112 extends C +case object C113 extends C +case object C114 extends C +case object C115 extends C +case object C116 extends C +case object C117 extends C +case object C118 extends C +case object C119 extends C +case object C120 extends C +case object C121 extends C +case object C122 extends C +case object C123 extends C +case object C124 extends C +case object C125 extends C +case object C126 extends C +case object C127 extends C +case object C128 extends C +case object C129 extends C +case object C130 extends C +case object C131 extends C +case object C132 extends C +case object C133 extends C +case object C134 extends C +case object C135 extends C +case object C136 extends C +case object C137 extends C +case object C138 extends C +case object C139 extends C +case object C140 extends C +case object C141 extends C +case object C142 extends C +case object C143 extends C +case object C144 extends C +case object C145 extends C +case object C146 extends C +case object C147 extends C +case object C148 extends C +case object C149 extends C +case object C150 extends C +case object C151 extends C +case object C152 extends C +case object C153 extends C +case object C154 extends C +case object C155 extends C +case object C156 extends C +case object C157 extends C +case object C158 extends C +case object C159 extends C +case object C160 extends C +case object C161 extends C +case object C162 extends C +case object C163 extends C +case object C164 extends C +case object C165 extends C +case object C166 extends C +case object C167 extends C +case object C168 extends C +case object C169 extends C +case object C170 extends C +case object C171 extends C +case object C172 extends C +case object C173 extends C +case object C174 extends C +case object C175 extends C +case object C176 extends C +case object C177 extends C +case object C178 extends C +case object C179 extends C +case object C180 extends C +case object C181 extends C +case object C182 extends C +case object C183 extends C +case object C184 extends C +case object C185 extends C +case object C186 extends C +case object C187 extends C +case object C188 extends C +case object C189 extends C +case object C190 extends C +case object C191 extends C +case object C192 extends C +case object C193 extends C +case object C194 extends C +case object C195 extends C +case object C196 extends C +case object C197 extends C +case object C198 extends C +case object C199 extends C +case object C200 extends C +case object C201 extends C +case object C202 extends C +case object C203 extends C +case object C204 extends C +case object C205 extends C +case object C206 extends C +case object C207 extends C +case object C208 extends C +case object C209 extends C +case object C210 extends C +case object C211 extends C +case object C212 extends C +case object C213 extends C +case object C214 extends C +case object C215 extends C +case object C216 extends C +case object C217 extends C +case object C218 extends C +case object C219 extends C +case object C220 extends C +case object C221 extends C +case object C222 extends C +case object C223 extends C +case object C224 extends C +case object C225 extends C +case object C226 extends C +case object C227 extends C +case object C228 extends C +case object C229 extends C +case object C230 extends C +case object C231 extends C +case object C232 extends C +case object C233 extends C +case object C234 extends C +case object C235 extends C +case object C236 extends C +case object C237 extends C +case object C238 extends C +case object C239 extends C +case object C240 extends C +case object C241 extends C +case object C242 extends C +case object C243 extends C +case object C244 extends C +case object C245 extends C +case object C246 extends C +case object C247 extends C +case object C248 extends C +case object C249 extends C +case object C250 extends C +case object C251 extends C +case object C252 extends C +case object C253 extends C +case object C254 extends C +case object C255 extends C +case object C256 extends C +case object C257 extends C +case object C258 extends C +case object C259 extends C +case object C260 extends C +case object C261 extends C +case object C262 extends C +case object C263 extends C +case object C264 extends C +case object C265 extends C +case object C266 extends C +case object C267 extends C +case object C268 extends C +case object C269 extends C +case object C270 extends C +case object C271 extends C +case object C272 extends C +case object C273 extends C +case object C274 extends C +case object C275 extends C +case object C276 extends C +case object C277 extends C +case object C278 extends C +case object C279 extends C +case object C280 extends C +case object C281 extends C +case object C282 extends C +case object C283 extends C +case object C284 extends C +case object C285 extends C +case object C286 extends C +case object C287 extends C +case object C288 extends C +case object C289 extends C +case object C290 extends C +case object C291 extends C +case object C292 extends C +case object C293 extends C +case object C294 extends C +case object C295 extends C +case object C296 extends C +case object C297 extends C +case object C298 extends C +case object C299 extends C +case object C300 extends C +case object C301 extends C +case object C302 extends C +case object C303 extends C +case object C304 extends C +case object C305 extends C +case object C306 extends C +case object C307 extends C +case object C308 extends C +case object C309 extends C +case object C310 extends C +case object C311 extends C +case object C312 extends C +case object C313 extends C +case object C314 extends C +case object C315 extends C +case object C316 extends C +case object C317 extends C +case object C318 extends C +case object C319 extends C +case object C320 extends C +case object C321 extends C +case object C322 extends C +case object C323 extends C +case object C324 extends C +case object C325 extends C +case object C326 extends C +case object C327 extends C +case object C328 extends C +case object C329 extends C +case object C330 extends C +case object C331 extends C +case object C332 extends C +case object C333 extends C +case object C334 extends C +case object C335 extends C +case object C336 extends C +case object C337 extends C +case object C338 extends C +case object C339 extends C +case object C340 extends C +case object C341 extends C +case object C342 extends C +case object C343 extends C +case object C344 extends C +case object C345 extends C +case object C346 extends C +case object C347 extends C +case object C348 extends C +case object C349 extends C +case object C350 extends C +case object C351 extends C +case object C352 extends C +case object C353 extends C +case object C354 extends C +case object C355 extends C +case object C356 extends C +case object C357 extends C +case object C358 extends C +case object C359 extends C +case object C360 extends C +case object C361 extends C +case object C362 extends C +case object C363 extends C +case object C364 extends C +case object C365 extends C +case object C366 extends C +case object C367 extends C +case object C368 extends C +case object C369 extends C +case object C370 extends C +case object C371 extends C +case object C372 extends C +case object C373 extends C +case object C374 extends C +case object C375 extends C +case object C376 extends C +case object C377 extends C +case object C378 extends C +case object C379 extends C +case object C380 extends C +case object C381 extends C +case object C382 extends C +case object C383 extends C +case object C384 extends C +case object C385 extends C +case object C386 extends C +case object C387 extends C +case object C388 extends C +case object C389 extends C +case object C390 extends C +case object C391 extends C +case object C392 extends C +case object C393 extends C +case object C394 extends C +case object C395 extends C +case object C396 extends C +case object C397 extends C +case object C398 extends C +case object C399 extends C +case object C400 extends C + +object M { + def f(c: C): Int = c match { + case C1 => 1 + case C2 => 2 + case C3 => 3 + case C4 => 4 + case C5 => 5 + case C6 => 6 + case C7 => 7 + case C8 => 8 + case C9 => 9 + case C10 => 10 + case C11 => 11 + case C12 => 12 + case C13 => 13 + case C14 => 14 + case C15 => 15 + case C16 => 16 + case C17 => 17 + case C18 => 18 + case C19 => 19 + case C20 => 20 + case C21 => 21 + case C22 => 22 + case C23 => 23 + case C24 => 24 + case C25 => 25 + case C26 => 26 + case C27 => 27 + case C28 => 28 + case C29 => 29 + case C30 => 30 + case C31 => 31 + case C32 => 32 + case C33 => 33 + case C34 => 34 + case C35 => 35 + case C36 => 36 + case C37 => 37 + case C38 => 38 + case C39 => 39 + case C40 => 40 + case C41 => 41 + case C42 => 42 + case C43 => 43 + case C44 => 44 + case C45 => 45 + case C46 => 46 + case C47 => 47 + case C48 => 48 + case C49 => 49 + case C50 => 50 + case C51 => 51 + case C52 => 52 + case C53 => 53 + case C54 => 54 + case C55 => 55 + case C56 => 56 + case C57 => 57 + case C58 => 58 + case C59 => 59 + case C60 => 60 + case C61 => 61 + case C62 => 62 + case C63 => 63 + case C64 => 64 + case C65 => 65 + case C66 => 66 + case C67 => 67 + case C68 => 68 + case C69 => 69 + case C70 => 70 + case C71 => 71 + case C72 => 72 + case C73 => 73 + case C74 => 74 + case C75 => 75 + case C76 => 76 + case C77 => 77 + case C78 => 78 + case C79 => 79 + case C80 => 80 + case C81 => 81 + case C82 => 82 + case C83 => 83 + case C84 => 84 + case C85 => 85 + case C86 => 86 + case C87 => 87 + case C88 => 88 + case C89 => 89 + case C90 => 90 + case C91 => 91 + case C92 => 92 + case C93 => 93 + case C94 => 94 + case C95 => 95 + case C96 => 96 + case C97 => 97 + case C98 => 98 + case C99 => 99 + case C100 => 100 + case C101 => 101 + case C102 => 102 + case C103 => 103 + case C104 => 104 + case C105 => 105 + case C106 => 106 + case C107 => 107 + case C108 => 108 + case C109 => 109 + case C110 => 110 + case C111 => 111 + case C112 => 112 + case C113 => 113 + case C114 => 114 + case C115 => 115 + case C116 => 116 + case C117 => 117 + case C118 => 118 + case C119 => 119 + case C120 => 120 + case C121 => 121 + case C122 => 122 + case C123 => 123 + case C124 => 124 + case C125 => 125 + case C126 => 126 + case C127 => 127 + case C128 => 128 + case C129 => 129 + case C130 => 130 + case C131 => 131 + case C132 => 132 + case C133 => 133 + case C134 => 134 + case C135 => 135 + case C136 => 136 + case C137 => 137 + case C138 => 138 + case C139 => 139 + case C140 => 140 + case C141 => 141 + case C142 => 142 + case C143 => 143 + case C144 => 144 + case C145 => 145 + case C146 => 146 + case C147 => 147 + case C148 => 148 + case C149 => 149 + case C150 => 150 + case C151 => 151 + case C152 => 152 + case C153 => 153 + case C154 => 154 + case C155 => 155 + case C156 => 156 + case C157 => 157 + case C158 => 158 + case C159 => 159 + case C160 => 160 + case C161 => 161 + case C162 => 162 + case C163 => 163 + case C164 => 164 + case C165 => 165 + case C166 => 166 + case C167 => 167 + case C168 => 168 + case C169 => 169 + case C170 => 170 + case C171 => 171 + case C172 => 172 + case C173 => 173 + case C174 => 174 + case C175 => 175 + case C176 => 176 + case C177 => 177 + case C178 => 178 + case C179 => 179 + case C180 => 180 + case C181 => 181 + case C182 => 182 + case C183 => 183 + case C184 => 184 + case C185 => 185 + case C186 => 186 + case C187 => 187 + case C188 => 188 + case C189 => 189 + case C190 => 190 + case C191 => 191 + case C192 => 192 + case C193 => 193 + case C194 => 194 + case C195 => 195 + case C196 => 196 + case C197 => 197 + case C198 => 198 + case C199 => 199 + case C200 => 200 + case C201 => 201 + case C202 => 202 + case C203 => 203 + case C204 => 204 + case C205 => 205 + case C206 => 206 + case C207 => 207 + case C208 => 208 + case C209 => 209 + case C210 => 210 + case C211 => 211 + case C212 => 212 + case C213 => 213 + case C214 => 214 + case C215 => 215 + case C216 => 216 + case C217 => 217 + case C218 => 218 + case C219 => 219 + case C220 => 220 + case C221 => 221 + case C222 => 222 + case C223 => 223 + case C224 => 224 + case C225 => 225 + case C226 => 226 + case C227 => 227 + case C228 => 228 + case C229 => 229 + case C230 => 230 + case C231 => 231 + case C232 => 232 + case C233 => 233 + case C234 => 234 + case C235 => 235 + case C236 => 236 + case C237 => 237 + case C238 => 238 + case C239 => 239 + case C240 => 240 + case C241 => 241 + case C242 => 242 + case C243 => 243 + case C244 => 244 + case C245 => 245 + case C246 => 246 + case C247 => 247 + case C248 => 248 + case C249 => 249 + case C250 => 250 + case C251 => 251 + case C252 => 252 + case C253 => 253 + case C254 => 254 + case C255 => 255 + case C256 => 256 + case C257 => 257 + case C258 => 258 + case C259 => 259 + case C260 => 260 + case C261 => 261 + case C262 => 262 + case C263 => 263 + case C264 => 264 + case C265 => 265 + case C266 => 266 + case C267 => 267 + case C268 => 268 + case C269 => 269 + case C270 => 270 + case C271 => 271 + case C272 => 272 + case C273 => 273 + case C274 => 274 + case C275 => 275 + case C276 => 276 + case C277 => 277 + case C278 => 278 + case C279 => 279 + case C280 => 280 + case C281 => 281 + case C282 => 282 + case C283 => 283 + case C284 => 284 + case C285 => 285 + case C286 => 286 + case C287 => 287 + case C288 => 288 + case C289 => 289 + case C290 => 290 + case C291 => 291 + case C292 => 292 + case C293 => 293 + case C294 => 294 + case C295 => 295 + case C296 => 296 + case C297 => 297 + case C298 => 298 + case C299 => 299 + case C300 => 300 + case C301 => 301 + case C302 => 302 + case C303 => 303 + case C304 => 304 + case C305 => 305 + case C306 => 306 + case C307 => 307 + case C308 => 308 + case C309 => 309 + case C310 => 310 + case C311 => 311 + case C312 => 312 + case C313 => 313 + case C314 => 314 + case C315 => 315 + case C316 => 316 + case C317 => 317 + case C318 => 318 + case C319 => 319 + case C320 => 320 + case C321 => 321 + case C322 => 322 + case C323 => 323 + case C324 => 324 + case C325 => 325 + case C326 => 326 + case C327 => 327 + case C328 => 328 + case C329 => 329 + case C330 => 330 + case C331 => 331 + case C332 => 332 + case C333 => 333 + case C334 => 334 + case C335 => 335 + case C336 => 336 + case C337 => 337 + case C338 => 338 + case C339 => 339 + case C340 => 340 + case C341 => 341 + case C342 => 342 + case C343 => 343 + case C344 => 344 + case C345 => 345 + case C346 => 346 + case C347 => 347 + case C348 => 348 + case C349 => 349 + case C350 => 350 + case C351 => 351 + case C352 => 352 + case C353 => 353 + case C354 => 354 + case C355 => 355 + case C356 => 356 + case C357 => 357 + case C358 => 358 + case C359 => 359 + case C360 => 360 + case C361 => 361 + case C362 => 362 + case C363 => 363 + case C364 => 364 + case C365 => 365 + case C366 => 366 + case C367 => 367 + case C368 => 368 + case C369 => 369 + case C370 => 370 + case C371 => 371 + case C372 => 372 + case C373 => 373 + case C374 => 374 + case C375 => 375 + case C376 => 376 + case C377 => 377 + case C378 => 378 + case C379 => 379 + case C380 => 380 + case C381 => 381 + case C382 => 382 + case C383 => 383 + case C384 => 384 + case C385 => 385 + case C386 => 386 + case C387 => 387 + case C388 => 388 + case C389 => 389 + case C390 => 390 + case C391 => 391 +// case C392 => 392 + case C393 => 393 + case C394 => 394 + case C395 => 395 + case C396 => 396 +// case C397 => 397 + case C398 => 398 + case C399 => 399 + case C400 => 400 + } +} diff --git a/tests/patmat/patmatexhaust.check b/tests/patmat/patmatexhaust.check new file mode 100644 index 000000000000..5121b73638a8 --- /dev/null +++ b/tests/patmat/patmatexhaust.check @@ -0,0 +1,33 @@ +./tests/patmat/patmatexhaust.scala:7: warning: match may not be exhaustive. +It would fail on the following input: Baz + def ma1(x:Foo) = x match { + ^ +./tests/patmat/patmatexhaust.scala:11: warning: match may not be exhaustive. +It would fail on the following input: Bar(_) + def ma2(x:Foo) = x match { + ^ +./tests/patmat/patmatexhaust.scala:23: warning: match may not be exhaustive. +It would fail on the following input: (Qult(), Qult()), (Kult(_), Kult(_)) + def ma3(x:Mult) = (x,x) match { // not exhaustive + ^ +./tests/patmat/patmatexhaust.scala:49: warning: match may not be exhaustive. +It would fail on the following input: _: Gp + def ma4(x:Deep) = x match { // missing cases: Gu, Gp which is not abstract so must be included + ^ +./tests/patmat/patmatexhaust.scala:75: warning: match may not be exhaustive. +It would fail on the following input: _: B + def ma9(x: B) = x match { + ^ +./tests/patmat/patmatexhaust.scala:100: warning: match may not be exhaustive. +It would fail on the following input: _: C1 + def ma10(x: C) = x match { // not exhaustive: C1 is not sealed. + ^ +./tests/patmat/patmatexhaust.scala:114: warning: match may not be exhaustive. +It would fail on the following input: _: C1 + def ma10(x: C) = x match { // not exhaustive: C1 has subclasses. + ^ +./tests/patmat/patmatexhaust.scala:126: warning: match may not be exhaustive. +It would fail on the following input: _: C1 + def ma10(x: C) = x match { // not exhaustive: C1 is not abstract. + ^ +8 warnings found \ No newline at end of file diff --git a/tests/patmat/patmatexhaust.scala b/tests/patmat/patmatexhaust.scala new file mode 100644 index 000000000000..26f0c12a919c --- /dev/null +++ b/tests/patmat/patmatexhaust.scala @@ -0,0 +1,131 @@ +class TestSealedExhaustive { // compile only + sealed abstract class Foo + + case class Bar(x:Int) extends Foo + case object Baz extends Foo + + def ma1(x:Foo) = x match { + case Bar(_) => // not exhaustive + } + + def ma2(x:Foo) = x match { + case Baz => // not exhaustive + } + + sealed abstract class Mult + case class Kult(s:Mult) extends Mult + case class Qult() extends Mult + + def ma33(x:Kult) = x match { // exhaustive + case Kult(_) => // exhaustive + } + + def ma3(x:Mult) = (x,x) match { // not exhaustive + case (Kult(_), Qult()) => // Kult missing + //case (Kult(_), Kult(_)) => + case (Qult(), Kult(_)) => // Qult missing + //case (Qult(), Qult()) => + } + + def ma3u(x:Mult) = ((x,x) : @unchecked) match { // not exhaustive, but not checked! + case (Kult(_), Qult()) => + case (Qult(), Kult(_)) => + } + + sealed abstract class Deep + + case object Ga extends Deep + sealed class Gp extends Deep + case object Gu extends Gp + + def zma3(x:Deep) = x match { // exhaustive! + case _ => + } + def zma4(x:Deep) = x match { // exhaustive! + case Ga => + case _ => + } + + def ma4(x:Deep) = x match { // missing cases: Gu, Gp which is not abstract so must be included + case Ga => + } + + def ma5(x:Deep) = x match { + case Gu => + case _ if 1 == 0 => + case Ga => + } + + def ma6() = List(1,2) match { // give up + case List(1,2) => + case x :: xs => + } + + def ma7() = List(1,2) match { //exhaustive + case 1::2::Nil => + case _ => + } + + sealed class B + case class B1() extends B + case object B2 extends B + def ma8(x: B) = x match { + case _: B => true + } + def ma9(x: B) = x match { + case B1() => true // missing B, which is not abstract so must be included + case B2 => true + } + + object ob1 { + sealed abstract class C + sealed abstract class C1 extends C + object C2 extends C + case class C3() extends C + case object C4 extends C + + def ma10(x: C) = x match { // exhaustive: abstract sealed C1 is dead end. + case C3() => true + case C2 | C4 => true + } + } + + object ob2 { + sealed abstract class C + abstract class C1 extends C + object C2 extends C + case class C3() extends C + case object C4 extends C + + def ma10(x: C) = x match { // not exhaustive: C1 is not sealed. + case C3() => true + case C2 | C4 => true + } + } + object ob3 { + sealed abstract class C + sealed abstract class C1 extends C + object D1 extends C1 + case class D2() extends C1 + object C2 extends C + case class C3() extends C + case object C4 extends C + + def ma10(x: C) = x match { // not exhaustive: C1 has subclasses. + case C3() => true + case C2 | C4 => true + } + } + object ob4 { + sealed abstract class C + sealed class C1 extends C + object C2 extends C + case class C3() extends C + case object C4 extends C + + def ma10(x: C) = x match { // not exhaustive: C1 is not abstract. + case C3() => true + case C2 | C4 => true + } + } +} diff --git a/tests/patmat/t3097.scala b/tests/patmat/t3097.scala new file mode 100644 index 000000000000..3ff61b3c7b87 --- /dev/null +++ b/tests/patmat/t3097.scala @@ -0,0 +1,35 @@ +sealed trait ISimpleValue + +sealed trait IListValue extends ISimpleValue { + def items: List[IAtomicValue[_]] +} + +sealed trait IAtomicValue[O] extends ISimpleValue { + def data: O +} + +sealed trait IAbstractDoubleValue[O] extends IAtomicValue[O] { +} + +sealed trait IDoubleValue extends IAbstractDoubleValue[Double] + +case class ListValue(val items: List[IAtomicValue[_]]) extends IListValue + +class DoubleValue(val data: Double) extends IDoubleValue { + def asDouble = data +} + +object Test { + + /** + * @param args the command line arguments + */ + def main(args: Array[String]): Unit = { + val v: ISimpleValue = new DoubleValue(1) + v match { + case m: IListValue => println("list") + case a: IAtomicValue[_] => println("atomic") + } + + } +} \ No newline at end of file diff --git a/tests/patmat/t3111.check b/tests/patmat/t3111.check new file mode 100644 index 000000000000..46ff0a6a9376 --- /dev/null +++ b/tests/patmat/t3111.check @@ -0,0 +1,8 @@ +./tests/patmat/t3111.scala:4: warning: match may not be exhaustive. +It would fail on the following input: false + bool match { + ^ +./tests/patmat/t3111.scala:11: warning: unreachable code + case _ => "cats and dogs living together... mass hysteria!" + ^ +two warnings found \ No newline at end of file diff --git a/tests/patmat/t3111.scala b/tests/patmat/t3111.scala new file mode 100644 index 000000000000..8f2bc5a273a9 --- /dev/null +++ b/tests/patmat/t3111.scala @@ -0,0 +1,13 @@ +object Test { + val bool: Boolean = false + + bool match { + case true => "true!" + } + + bool match { + case true => "true!" + case false => "false!" + case _ => "cats and dogs living together... mass hysteria!" + } +} \ No newline at end of file diff --git a/tests/patmat/t3163.check b/tests/patmat/t3163.check new file mode 100644 index 000000000000..3da94e2c2a40 --- /dev/null +++ b/tests/patmat/t3163.check @@ -0,0 +1,5 @@ +./tests/patmat/t3163.scala:2: warning: match may not be exhaustive. +It would fail on the following input: _: AnyVal + def foo(x : AnyVal) = x match {case b : Boolean => "It's a bool"} + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t3163.scala b/tests/patmat/t3163.scala new file mode 100644 index 000000000000..2e0f2c1d9482 --- /dev/null +++ b/tests/patmat/t3163.scala @@ -0,0 +1,3 @@ +object Test { + def foo(x : AnyVal) = x match {case b : Boolean => "It's a bool"} +} \ No newline at end of file diff --git a/tests/patmat/t3683.scala b/tests/patmat/t3683.scala new file mode 100644 index 000000000000..44be9d6c63fe --- /dev/null +++ b/tests/patmat/t3683.scala @@ -0,0 +1,19 @@ +sealed trait Foo +sealed trait Bar extends Foo +sealed trait W[T >: Bar <: Foo] +sealed case class X() extends W[Foo] +sealed case class Y() extends W[Bar] +sealed case class Z[T >: Bar <: Foo]( + z1: W[T] +) extends W[T] + +object Main { + def func(w: W[Bar]): Int = { + w match { + // Error if I include it, warning if I do not! + // case X() => 2 + case Y() => 1 + case Z(z) => func(z) + } + } +} diff --git a/tests/patmat/t4020.scala b/tests/patmat/t4020.scala new file mode 100644 index 000000000000..f9764601910a --- /dev/null +++ b/tests/patmat/t4020.scala @@ -0,0 +1,25 @@ +class A { + sealed trait Foo +} + +object a1 extends A { + case class Foo1(i: Int) extends Foo +} + +object a2 extends A { + case class Foo2(i: Int) extends Foo +} + +class B { + def mthd(foo: a2.Foo) = { + foo match { + case a2.Foo2(i) => i + + // Note: This case is impossible. In fact, scalac + // will (correctly) report an error if it is uncommented, + // but a warning if it is commented. + + // case a1.Foo1(i) => i + } + } +} \ No newline at end of file diff --git a/tests/patmat/t4333.scala.ignore b/tests/patmat/t4333.scala.ignore new file mode 100644 index 000000000000..07d105c74e47 --- /dev/null +++ b/tests/patmat/t4333.scala.ignore @@ -0,0 +1,7 @@ +object Enum extends Enumeration { val A, B, C = Value } + +object Test { + def foo(v : Enum.Value) = v match { + case Enum.B => println("B") + } +} diff --git a/tests/patmat/t4408.check b/tests/patmat/t4408.check new file mode 100644 index 000000000000..53bfe1c2c564 --- /dev/null +++ b/tests/patmat/t4408.check @@ -0,0 +1,5 @@ +./tests/patmat/t4408.scala:2: warning: match may not be exhaustive. +It would fail on the following input: List(_, _, _) + def printList(in: List[String]): Unit = in match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t4408.scala b/tests/patmat/t4408.scala new file mode 100644 index 000000000000..419b6636971e --- /dev/null +++ b/tests/patmat/t4408.scala @@ -0,0 +1,16 @@ +object Test { + def printList(in: List[String]): Unit = in match { + case Nil => Unit + + case (s: String) :: Nil => + println(s) + + case head :: (s: String) :: Nil => + printList(head :: Nil) + for(i <- head){ + print(i) + } + println + println(s) + } +} diff --git a/tests/patmat/t4526.check b/tests/patmat/t4526.check new file mode 100644 index 000000000000..b577cbc0c121 --- /dev/null +++ b/tests/patmat/t4526.check @@ -0,0 +1,13 @@ +./tests/patmat/t4526.scala:2: warning: match may not be exhaustive. +It would fail on the following input: _: Int + def foo(a: Int) = a match { + ^ +./tests/patmat/t4526.scala:7: warning: match may not be exhaustive. +It would fail on the following input: (_, _) + def bar(a: (Int, Int)) = a match { + ^ +./tests/patmat/t4526.scala:12: warning: match may not be exhaustive. +It would fail on the following input: (false, false), (true, true) + def baz(a: (Boolean, Boolean)) = a match { + ^ +three warnings found \ No newline at end of file diff --git a/tests/patmat/t4526.scala b/tests/patmat/t4526.scala new file mode 100644 index 000000000000..d531c6b34304 --- /dev/null +++ b/tests/patmat/t4526.scala @@ -0,0 +1,16 @@ +object Test{ + def foo(a: Int) = a match { + case 5 => "Five!" + case 42 => "The answer." + } + + def bar(a: (Int, Int)) = a match { + case (5, 5) => "Two fives!" + case (42, 21) => "The answer and a half." + } + + def baz(a: (Boolean, Boolean)) = a match { + case (true, false) => "tf" + case (false, true) => "ft" + } +} \ No newline at end of file diff --git a/tests/patmat/t4691.check b/tests/patmat/t4691.check new file mode 100644 index 000000000000..4d2c24506810 --- /dev/null +++ b/tests/patmat/t4691.check @@ -0,0 +1,5 @@ +./tests/patmat/t4691.scala:15: warning: match may not be exhaustive. +It would fail on the following input: NodeType2(_) + def test (x: Node) = x match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t4691.scala b/tests/patmat/t4691.scala new file mode 100644 index 000000000000..bfaa61670057 --- /dev/null +++ b/tests/patmat/t4691.scala @@ -0,0 +1,18 @@ +sealed trait Node + +class NodeType1 (val a:Int) extends Node +class NodeType2 (val b:Int) extends Node + +object NodeType1 { + def unapply (x : NodeType1) : Some[Int] = Some(x.a) +} + +object NodeType2 { + def unapply (x : NodeType2) : Some[Int] = Some(x.b) +} + +object Test { + def test (x: Node) = x match { + case NodeType1(a) => "got node type 1 " + a + } +} \ No newline at end of file diff --git a/tests/patmat/t5440.check b/tests/patmat/t5440.check new file mode 100644 index 000000000000..f7d97eef78f3 --- /dev/null +++ b/tests/patmat/t5440.check @@ -0,0 +1,5 @@ +./tests/patmat/t5440.scala:3: warning: match may not be exhaustive. +It would fail on the following input: { (Nil, List(_)), (List(_), Nil) } + (list1, list2) match { + ^ +one warning found diff --git a/tests/patmat/t5968.scala b/tests/patmat/t5968.scala new file mode 100644 index 000000000000..14cc903c8033 --- /dev/null +++ b/tests/patmat/t5968.scala @@ -0,0 +1,7 @@ +object Test { + object X + def f(e: Either[Int, X.type]) = e match { + case Left(i) => i + case Right(X) => 0 + } +} \ No newline at end of file diff --git a/tests/patmat/t6008.scala b/tests/patmat/t6008.scala new file mode 100644 index 000000000000..c42e9c5a5991 --- /dev/null +++ b/tests/patmat/t6008.scala @@ -0,0 +1,5 @@ +object Test { + def x(in: (Int, Boolean)) = in match { + case (i: Int, b: Boolean) => 3 + } +} \ No newline at end of file diff --git a/tests/patmat/t6420.check b/tests/patmat/t6420.check new file mode 100644 index 000000000000..c62b33d181a5 --- /dev/null +++ b/tests/patmat/t6420.check @@ -0,0 +1,5 @@ +./tests/patmat/t6420.scala:5: warning: match may not be exhaustive. +It would fail on the following input: (Nil, _), (List(_, _), _), (Nil, Nil), (Nil, List(_, _)), (List(_, _), Nil), (List(_, _), List(_, _)), (_, Nil), (_, List(_, _)) + def foo(x: List[Boolean], y: List[Boolean]) = (x,y) match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t6420.scala b/tests/patmat/t6420.scala new file mode 100644 index 000000000000..80c0f90f694b --- /dev/null +++ b/tests/patmat/t6420.scala @@ -0,0 +1,11 @@ +object Test { + val c0 = false + val c1 = true + + def foo(x: List[Boolean], y: List[Boolean]) = (x,y) match { + case (`c0`::x, `c0`::y) => x + case (`c0`::x, `c1`::y) => y + case (`c1`::x, `c0`::y) => y + case (`c1`::x, `c1`::y) => x + } +} diff --git a/tests/patmat/t6450.scala b/tests/patmat/t6450.scala new file mode 100644 index 000000000000..157f1ce8124e --- /dev/null +++ b/tests/patmat/t6450.scala @@ -0,0 +1,9 @@ +sealed abstract class FoundNode[T] +// case class A[T](x: T) extends FoundNode[T] + +object Foo { + val v: (Some[_], FoundNode[_]) = (???, ???) + v match { + case (x: Some[t], _) => + } +} diff --git a/tests/patmat/t6818.scala b/tests/patmat/t6818.scala new file mode 100644 index 000000000000..2334095c46d8 --- /dev/null +++ b/tests/patmat/t6818.scala @@ -0,0 +1,11 @@ +object Test { + type Id[X] = X + + def foo(x:Id[Option[Int]]) = x match { + case Some(n) => "foo" + case None => "bar" + } + + foo(Some(3)) // "foo" + foo(None) // "bar" +} \ No newline at end of file diff --git a/tests/patmat/t7206.scala.ignore b/tests/patmat/t7206.scala.ignore new file mode 100644 index 000000000000..0133f1808e7a --- /dev/null +++ b/tests/patmat/t7206.scala.ignore @@ -0,0 +1,19 @@ +object E extends Enumeration { + val V = Value +} + +sealed case class C(e: E.Value) + +class Test { + def foo(c: C) = { + c match { + case C(E.V) => {} + } + } + + def foo2(e: E.Value) = { + e match { + case E.V => {} + } + } +} diff --git a/tests/patmat/t7285.check b/tests/patmat/t7285.check new file mode 100644 index 000000000000..703706cdc532 --- /dev/null +++ b/tests/patmat/t7285.check @@ -0,0 +1,13 @@ +./tests/patmat/t7285.scala:15: warning: match may not be exhaustive. +It would fail on the following input: (Up, Down) + (d1, d2) match { + ^ +./tests/patmat/t7285.scala:33: warning: match may not be exhaustive. +It would fail on the following input: Down + (d1) match { + ^ +./tests/patmat/t7285.scala:51: warning: match may not be exhaustive. +It would fail on the following input: (Base.Up, Base.Down) + (d1, d2) match { + ^ +three warnings found \ No newline at end of file diff --git a/tests/patmat/t7285.scala b/tests/patmat/t7285.scala new file mode 100644 index 000000000000..d40df7fe8919 --- /dev/null +++ b/tests/patmat/t7285.scala @@ -0,0 +1,55 @@ +sealed abstract class Base + + +object Test1 { + sealed abstract class Base + + object Base { + case object Down extends Base { + } + + case object Up extends Base { + } + + def foo(d1: Base, d2: Base) = + (d1, d2) match { + case (Up, Up) | (Down, Down) => false + case (Down, Up) => true + } + } +} + +object Test2 { + sealed abstract class Base + + object Base { + case object Down extends Base { + } + + case object Up extends Base { + } + + def foo(d1: Base, d2: Base) = + (d1) match { + case Test2.Base.Up => false + } + } +} + + +object Test4 { + sealed abstract class Base + + object Base { + case object Down extends Base + + case object Up extends Base + } + + import Test4.Base._ + def foo(d1: Base, d2: Base) = + (d1, d2) match { + case (Up, Up) | (Down, Down) => false + case (Down, Test4.Base.Up) => true + } +} diff --git a/tests/patmat/t7285a.scala b/tests/patmat/t7285a.scala new file mode 100644 index 000000000000..49f6b663b28c --- /dev/null +++ b/tests/patmat/t7285a.scala @@ -0,0 +1,83 @@ +sealed abstract class Base + +object Test { + case object Up extends Base + + def foo(d1: Base) = + d1 match { + case Up => + } + + // Sealed subtype: ModuleTypeRef .this.Test.Up.type + // Pattern: UniqueThisType Test.this.type +} + + +object Test1 { + sealed abstract class Base + + object Base { + case object Down extends Base { + } + + case object Up extends Base { + } + + def foo(d1: Base, d2: Base) = + (d1, d2) match { + case (Up, Up) | (Down, Down) => false + case (Down, Up) => true + case (Up, Down) => false + } + } +} + +object Test2 { + sealed abstract class Base + + object Base { + case object Down extends Base { + } + + case object Up extends Base { + } + + def foo(d1: Base, d2: Base) = + (d1) match { + case Up | Down => false + } + } +} + +object Test3 { + sealed abstract class Base + + object Base { + case object Down extends Base + + def foo(d1: Base, d2: Base) = + (d1, d2) match { + case (Down, Down) => false + } + } +} + +object Test4 { + sealed abstract class Base + + object Base { + case object Down extends Base { + } + + case object Up extends Base { + } + + } + import Test4.Base._ + def foo(d1: Base, d2: Base) = + (d1, d2) match { + case (Up, Up) | (Down, Down) => false + case (Down, Test4.Base.Up) => true + case (Up, Down) => false + } +} diff --git a/tests/patmat/t7298.scala b/tests/patmat/t7298.scala new file mode 100644 index 000000000000..6fba5e120ca3 --- /dev/null +++ b/tests/patmat/t7298.scala @@ -0,0 +1,11 @@ +sealed trait Bool + +object Bool { + case object FALSE extends Bool + case object TRUE extends Bool + + def show(b: Bool) = b match { + case FALSE => "1" + case TRUE => "2" + } +} diff --git a/tests/patmat/t7353.scala b/tests/patmat/t7353.scala new file mode 100644 index 000000000000..7a8fea115fc4 --- /dev/null +++ b/tests/patmat/t7353.scala @@ -0,0 +1,11 @@ +sealed trait EthernetType + +object EthernetType { + final case object Gigabit extends EthernetType + final case object FastEthernet extends EthernetType + + final def toInt(t: EthernetType) = t match { + case Gigabit => 1 + case FastEthernet => 2 + } +} \ No newline at end of file diff --git a/tests/patmat/t7437.scala b/tests/patmat/t7437.scala new file mode 100644 index 000000000000..b0c5dff7cbbc --- /dev/null +++ b/tests/patmat/t7437.scala @@ -0,0 +1,17 @@ +sealed trait IntegralNumber +sealed trait FiniteNumber extends IntegralNumber + +object IntegralNumber { + + sealed abstract class BaseNumber extends IntegralNumber + sealed abstract class NonFinite extends BaseNumber + object NaN extends NonFinite + sealed abstract class FiniteNumberImpl[N](val value: N) extends BaseNumber with FiniteNumber + sealed class IntNumber(value: Int) extends FiniteNumberImpl[Int](value) + + def test(t: IntNumber, o: IntegralNumber) = o match { + case NaN => -1 + case o: IntNumber => t.value.compare(o.value) + } + +} \ No newline at end of file diff --git a/tests/patmat/t7466.check b/tests/patmat/t7466.check new file mode 100644 index 000000000000..93f7fd62a072 --- /dev/null +++ b/tests/patmat/t7466.check @@ -0,0 +1,5 @@ +./tests/patmat/t7466.scala:8: warning: match may not be exhaustive. +It would fail on the following input: (true, _), (false, _), (_, true), (_, false) + (b1, b2) match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t7466.scala b/tests/patmat/t7466.scala new file mode 100644 index 000000000000..a74bf4ee210f --- /dev/null +++ b/tests/patmat/t7466.scala @@ -0,0 +1,17 @@ +object Test extends App { + val Yes1 = true + val Yes2 = true + val No1 = false + val No2 = false + + def test(b1: Boolean, b2: Boolean) = { + (b1, b2) match { + case (No1, No2) => println("1") + case (No1, Yes2) => println("2") + case (Yes1, No2) => println("3") + case (Yes1, Yes2) => println("4") + } + } + + test(No1, Yes2) +} \ No newline at end of file diff --git a/tests/patmat/t7631.check b/tests/patmat/t7631.check new file mode 100644 index 000000000000..ede3703e2a60 --- /dev/null +++ b/tests/patmat/t7631.check @@ -0,0 +1,5 @@ +./tests/patmat/t7631.scala:8: warning: match may not be exhaustive. +It would fail on the following input: TestB() + val x = input match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t7631.scala b/tests/patmat/t7631.scala new file mode 100644 index 000000000000..13e74183f084 --- /dev/null +++ b/tests/patmat/t7631.scala @@ -0,0 +1,11 @@ +sealed trait Test +case class TestA() extends Test +case class TestB() extends Test + +object Tester { + val input : Test = TestA() + val num = 3 + val x = input match { + case TestA() if num == 3 => 2 + } +} \ No newline at end of file diff --git a/tests/patmat/t7669.check b/tests/patmat/t7669.check new file mode 100644 index 000000000000..2804dbf5c3ac --- /dev/null +++ b/tests/patmat/t7669.check @@ -0,0 +1,5 @@ +./tests/patmat/t7669.scala:10: warning: match may not be exhaustive. +It would fail on the following input: NotHandled(_) + def exhausto(expr: Expr): Unit = expr match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t7669.scala b/tests/patmat/t7669.scala new file mode 100644 index 000000000000..3aa74129ed9b --- /dev/null +++ b/tests/patmat/t7669.scala @@ -0,0 +1,14 @@ +object Test { + + sealed abstract class Expr + // Change type of `arg` to `Any` and the exhaustiveness warning + // is issued below + case class Op(arg: Expr) extends Expr + case class NotHandled(num: Double) extends Expr + + + def exhausto(expr: Expr): Unit = expr match { + case Op(Op(_)) => + case Op(_) => + } +} diff --git a/tests/patmat/t7746.check b/tests/patmat/t7746.check new file mode 100644 index 000000000000..be4c53570c68 --- /dev/null +++ b/tests/patmat/t7746.check @@ -0,0 +1,5 @@ +./tests/patmat/t7746.scala:2: warning: match may not be exhaustive. +It would fail on the following input: Some(_), None + def f[T](x: Option[T]) = x match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t7746.scala b/tests/patmat/t7746.scala new file mode 100644 index 000000000000..91f3823a47e9 --- /dev/null +++ b/tests/patmat/t7746.scala @@ -0,0 +1,5 @@ +object Test { + def f[T](x: Option[T]) = x match { + case Some(Some(5)) => true + } +} \ No newline at end of file diff --git a/tests/patmat/t8068.scala b/tests/patmat/t8068.scala new file mode 100644 index 000000000000..9837b7381413 --- /dev/null +++ b/tests/patmat/t8068.scala @@ -0,0 +1,14 @@ +trait K[A] { + sealed trait T + case class C(x: Int) extends T + case object O extends T +} + +object Hello { + def f[A](k: K[A])(t: k.T) = { + t match { + case k.C(x) => ??? + case k.O => ??? + } + } +} diff --git a/tests/patmat/t8178.check b/tests/patmat/t8178.check new file mode 100644 index 000000000000..963845f5310c --- /dev/null +++ b/tests/patmat/t8178.check @@ -0,0 +1,13 @@ +./tests/patmat/t8178.scala:6: warning: match may not be exhaustive. +It would fail on the following input: FailsChild2(_) + f match { + ^ +./tests/patmat/t8178.scala:14: warning: match may not be exhaustive. +It would fail on the following input: VarArgs1(_) + f match { + ^ +./tests/patmat/t8178.scala:27: warning: match may not be exhaustive. +It would fail on the following input: SeqArgs2(_) + f match { + ^ +three warnings found \ No newline at end of file diff --git a/tests/patmat/t8178.scala b/tests/patmat/t8178.scala new file mode 100644 index 000000000000..4fb39955bc0e --- /dev/null +++ b/tests/patmat/t8178.scala @@ -0,0 +1,33 @@ +sealed trait Fails +case class VarArgs1(a: String*) extends Fails +case class FailsChild2(a: Seq[String]) extends Fails +object FailsTest { + def matchOnVarArgsFirstFails(f: Fails) = { + f match { + case VarArgs1(_) => ??? + // BUG: Without this line we should get a non-exhaustive match compiler error. + //case FailsChild2(_) => ??? + } + } + + def matchOnSeqArgsFirstWorks(f: Fails) = { + f match { + case FailsChild2(_) => ??? + // Without this line, the compiler reports a "match may not be exhaustive" error as expected. + // case VarArgs1(_) => ??? + } + } +} + +sealed trait Works +case class SeqArgs1(a: Seq[String]) extends Works +case class SeqArgs2(a: Seq[String]) extends Works +object WorksTest { + def matcher(f: Works) = { + f match { + case SeqArgs1(_) => ??? + // Without this line, the compiler reports a "match may not be exhaustive" error as expected. + // case SeqArgs2(_) => ??? + } + } +} \ No newline at end of file diff --git a/tests/patmat/t8412.check b/tests/patmat/t8412.check new file mode 100644 index 000000000000..b82b33999613 --- /dev/null +++ b/tests/patmat/t8412.check @@ -0,0 +1,5 @@ +./tests/patmat/t8412.scala:7: warning: match may not be exhaustive. +It would fail on the following input: Lit(_) + tree match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t8412.scala b/tests/patmat/t8412.scala new file mode 100644 index 000000000000..f4b2b60907ff --- /dev/null +++ b/tests/patmat/t8412.scala @@ -0,0 +1,14 @@ +sealed trait Tree +case class Let(sth: List[Any]) extends Tree +case class Lit(sth: Any) extends Tree + +object Test { + def wroong(tree: Tree) = + tree match { + case Let(_ :: rest) => + ??? + case Let(Nil) => + ??? + // no warning for missing Lit(_) in 2.10 + } +} diff --git a/tests/patmat/t8430.check b/tests/patmat/t8430.check new file mode 100644 index 000000000000..4493062bfceb --- /dev/null +++ b/tests/patmat/t8430.check @@ -0,0 +1,5 @@ +./tests/patmat/t8430.scala:15: warning: match may not be exhaustive. +It would fail on the following input: LetF, LetC, LetP, LetL(UnitLit), LetL(BooleanLit), LetL(IntLit) + def transform(tree: Tree) : Any = tree match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t8430.scala b/tests/patmat/t8430.scala new file mode 100644 index 000000000000..ccd4585d94b0 --- /dev/null +++ b/tests/patmat/t8430.scala @@ -0,0 +1,19 @@ +sealed trait CL3Literal +case object IntLit extends CL3Literal +case object CharLit extends CL3Literal +case object BooleanLit extends CL3Literal +case object UnitLit extends CL3Literal + + +sealed trait Tree +case class LetL(value: CL3Literal) extends Tree +case object LetP extends Tree +case object LetC extends Tree +case object LetF extends Tree + +object Test { + def transform(tree: Tree) : Any = tree match { + case LetL(CharLit) => + ??? + } +} diff --git a/tests/patmat/t8511.check b/tests/patmat/t8511.check new file mode 100644 index 000000000000..df07d019aa33 --- /dev/null +++ b/tests/patmat/t8511.check @@ -0,0 +1,5 @@ +./tests/patmat/t8511.scala:18: warning: match may not be exhaustive. +It would fail on the following input: Baz(), Bar(_) + private def logic(head: Expr): String = head match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t8511.scala b/tests/patmat/t8511.scala new file mode 100644 index 000000000000..bc7f64713441 --- /dev/null +++ b/tests/patmat/t8511.scala @@ -0,0 +1,25 @@ +sealed trait Expr +final case class Foo(other: Option[String]) extends Expr +final case class Bar(someConstant: String) extends Expr +final case class Baz() extends Expr +final case class EatsExhaustiveWarning(other: Reference) extends Expr + +sealed trait Reference { + val value: String +} + +object Reference { + def unapply(reference: Reference): Option[(String)] = { + Some(reference.value) + } +} + +object EntryPoint { + private def logic(head: Expr): String = head match { + case Foo(_) => + ??? + // Commenting this line only causes the exhaustive search warning to be emitted + case EatsExhaustiveWarning(Reference(text)) => + ??? + } +} \ No newline at end of file diff --git a/tests/patmat/t8546.scala b/tests/patmat/t8546.scala new file mode 100644 index 000000000000..c39d749b4c45 --- /dev/null +++ b/tests/patmat/t8546.scala @@ -0,0 +1,49 @@ +package test + +class F1() { + private sealed abstract class T + private case class A(m: Int) extends T + private case class B() extends T + private case object C extends T + + // No warnings here + private def foo(t: T) = t match { + case A(m) => println("A:" + m) + case B() => println("B") + case C => println("C") + } + + def test(m: Int): Unit = { + foo(A(m)) + foo(B()) + foo(C) + } +} + +class F2[M]() { + private sealed abstract class T + private case class A(m: M) extends T + private case class B() extends T + private case object C extends T + + // match may not be exhaustive. It would fail on the following input: C + private def foo(t: T) = t match { + case A(m) => println("A:" + m) + case B() => println("B") + case C => println("C") + } + + def test(m: M): Unit = { + foo(A(m)) + foo(B()) + foo(C) + } + +} + +object Test { + def main(args: Array[String]): Unit = { + new F1().test(1) + new F2[Int]().test(1) + } +} \ No newline at end of file diff --git a/tests/patmat/t8606.scala b/tests/patmat/t8606.scala new file mode 100644 index 000000000000..9388c9f02bd0 --- /dev/null +++ b/tests/patmat/t8606.scala @@ -0,0 +1,18 @@ +class Cl[T] { + + sealed trait A { + def foo = this match { + case AObj => 0 + case BObj => 0 + case ACls(x) => 0 + case BCls(x) => 0 + } + } + + case object AObj extends A + case class ACls(x: Int) extends A + + sealed trait B extends A + case object BObj extends B + case class BCls(x: Int) extends B +} diff --git a/tests/patmat/t9129.check b/tests/patmat/t9129.check new file mode 100644 index 000000000000..aa722a61acad --- /dev/null +++ b/tests/patmat/t9129.check @@ -0,0 +1,5 @@ +./tests/patmat/t9129.scala:21: warning: match may not be exhaustive. +It would fail on the following input: Two(B2, A2), Two(_, A2) + def foo(c: C): Unit = c match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t9129.scala b/tests/patmat/t9129.scala new file mode 100644 index 000000000000..89f08f0ac2db --- /dev/null +++ b/tests/patmat/t9129.scala @@ -0,0 +1,29 @@ +object Test { + + sealed abstract class A + + case object A1 extends A + + case object A2 extends A + + sealed abstract class B + + case object B1 extends B + + case object B2 extends B + + sealed abstract class C + + final case class One(a: A, b: B) extends C + + final case class Two(b: B, a: A) extends C + + def foo(c: C): Unit = c match { + case One(A1, B1) => + case One(A2, B1) => + case One(A1, B2) => + case One(A2, B2) => + case Two(B1, A1) => + case Two(B2, A1) => + } +} \ No newline at end of file diff --git a/tests/patmat/t9232.check b/tests/patmat/t9232.check new file mode 100644 index 000000000000..c3957c0ffa1d --- /dev/null +++ b/tests/patmat/t9232.check @@ -0,0 +1,5 @@ +./tests/patmat/t9232.scala:13: warning: match may not be exhaustive. +It would fail on the following input: Node2() + def transformTree(tree: Tree): Any = tree match { + ^ +one warning found diff --git a/tests/patmat/t9232.scala b/tests/patmat/t9232.scala new file mode 100644 index 000000000000..975ec58db823 --- /dev/null +++ b/tests/patmat/t9232.scala @@ -0,0 +1,16 @@ +final class Foo(val value: Int) + +object Foo { + def unapplySeq(foo: Foo): Some[Seq[Int]] = Some(List(foo.value)) + // def unapply(foo: Foo): Some[Int] = Some(foo.value) +} + +sealed trait Tree +case class Node1(foo: Foo) extends Tree +case class Node2() extends Tree + +object Test { + def transformTree(tree: Tree): Any = tree match { + case Node1(Foo(1)) => ??? + } +} diff --git a/tests/patmat/t9289.check b/tests/patmat/t9289.check new file mode 100644 index 000000000000..5240988e2606 --- /dev/null +++ b/tests/patmat/t9289.check @@ -0,0 +1,9 @@ +./tests/patmat/t9289.scala:9: warning: match may not be exhaustive. +It would fail on the following input: module.LetR() + def patmat(tree: module.Tree) = tree match { + ^ +./tests/patmat/t9289.scala:20: warning: match may not be exhaustive. +It would fail on the following input: module.LetR() + def patmat(tree: module.Tree) = tree match { + ^ +two warnings found \ No newline at end of file diff --git a/tests/patmat/t9289.scala b/tests/patmat/t9289.scala new file mode 100644 index 000000000000..714a4a0e3928 --- /dev/null +++ b/tests/patmat/t9289.scala @@ -0,0 +1,28 @@ +trait Module { + sealed trait Tree + + case class LetL() extends Tree + case class LetR() extends Tree +} + +class Patmat[T <: Module](val module: T) { + def patmat(tree: module.Tree) = tree match { + case module.LetL() => ??? + } + + def exhaust(tree: module.Tree) = tree match { + case module.LetL() => ??? + case module.LetR() => ??? + } +} + +class Patmat2(val module: Module) { + def patmat(tree: module.Tree) = tree match { + case module.LetL() => ??? + } + + def exhaust(tree: module.Tree) = tree match { + case module.LetL() => ??? + case module.LetR() => ??? + } +} diff --git a/tests/patmat/t9351.check b/tests/patmat/t9351.check new file mode 100644 index 000000000000..03b94c2c001b --- /dev/null +++ b/tests/patmat/t9351.check @@ -0,0 +1,13 @@ +./tests/patmat/t9351.scala:8: warning: match may not be exhaustive. +It would fail on the following input: _: A + a match { + ^ +./tests/patmat/t9351.scala:17: warning: match may not be exhaustive. +It would fail on the following input: (_, _), (_, None), (_, Some(_)) + (a, o) match { + ^ +./tests/patmat/t9351.scala:28: warning: match may not be exhaustive. +It would fail on the following input: (_, _) + (a, b) match { + ^ +three warnings found \ No newline at end of file diff --git a/tests/patmat/t9351.scala b/tests/patmat/t9351.scala new file mode 100644 index 000000000000..9b9bd4312ae7 --- /dev/null +++ b/tests/patmat/t9351.scala @@ -0,0 +1,35 @@ +trait A {} +case object B extends A {} +case object C extends A {} + +class X { + def good = { + val a: A = B + a match { + case B => + case C => + } + } + + def bad = { + val a: A = B + val o: Option[Int] = None + (a, o) match { + case (B, None) => + case (B, Some(_)) => + case (C, None) => + case (C, Some(_)) => + } + } + + def alsoGood = { + val a: A = B + val b: A = C + (a, b) match { + case (B, B) => + case (B, C) => + case (C, B) => + case (C, C) => + } + } +} diff --git a/tests/patmat/t9398.check b/tests/patmat/t9398.check new file mode 100644 index 000000000000..0efbf231d6c5 --- /dev/null +++ b/tests/patmat/t9398.check @@ -0,0 +1,5 @@ +./tests/patmat/t9398.scala:11: warning: match may not be exhaustive. +It would fail on the following input: CC(_, B2) + case CC(_, B) => () + ^ +one warning found diff --git a/tests/patmat/t9398.scala b/tests/patmat/t9398.scala new file mode 100644 index 000000000000..6d4d6bd3b111 --- /dev/null +++ b/tests/patmat/t9398.scala @@ -0,0 +1,13 @@ +sealed abstract class TA +sealed abstract class TB extends TA +case object B extends TB +case object B2 extends TB + +case class CC(i: Int, tb: TB) + +object Test { + // Should warn that CC(_, B2) isn't matched + def foo: CC => Unit = { + case CC(_, B) => () + } +} \ No newline at end of file diff --git a/tests/patmat/t9399.scala b/tests/patmat/t9399.scala new file mode 100644 index 000000000000..89dbedd9672f --- /dev/null +++ b/tests/patmat/t9399.scala @@ -0,0 +1,16 @@ +sealed abstract class TA +sealed abstract class TB extends TA +case object A extends TA +case object B extends TB + +sealed trait C +case class CTA(id: Int, da: TA) extends C +case class CTB(id: Int, da: TB) extends C + +object Test { + val test: C => Unit = { + case CTA(_, A) => + case CTA(_, B) => + case CTB(_, B) => + } +} diff --git a/tests/patmat/t9573.check b/tests/patmat/t9573.check new file mode 100644 index 000000000000..4ec379161422 --- /dev/null +++ b/tests/patmat/t9573.check @@ -0,0 +1,5 @@ +./tests/patmat/t9573.scala:9: warning: match may not be exhaustive. +It would fail on the following input: Horse(_) + x match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t9573.scala b/tests/patmat/t9573.scala new file mode 100644 index 000000000000..2a32c2599f23 --- /dev/null +++ b/tests/patmat/t9573.scala @@ -0,0 +1,13 @@ +class Foo { + + def foo = { + abstract sealed class Animal + case class Goat(age: Int) extends Animal + case class Horse(age: Int) extends Animal + + val x: Animal = Goat(1) + x match { + case Goat(_) => println("a goat") + } + } +} \ No newline at end of file diff --git a/tests/patmat/t9630.scala b/tests/patmat/t9630.scala new file mode 100644 index 000000000000..c846faa99350 --- /dev/null +++ b/tests/patmat/t9630.scala @@ -0,0 +1,21 @@ +sealed trait OpError +sealed trait RequestErrorType +sealed trait ProcessingErrorType + +final case class InvalidEndpoint(reason: String) extends RequestErrorType +final case class InvalidParameters(reason: String) extends RequestErrorType + +final case class InvalidFormat(response: String) extends ProcessingErrorType +final case class EntityNotFound(id: Long) extends ProcessingErrorType + +final case class RequestError(errorType: RequestErrorType) extends OpError +final case class ProcessingError(errorType: ProcessingErrorType) extends OpError + +object Test{ + def printMatches(error: OpError): Unit = error match { + case RequestError(InvalidEndpoint(reason)) => //print something + case RequestError(InvalidParameters(reason)) => //print something + case ProcessingError(InvalidFormat(format)) => //print something + case ProcessingError(EntityNotFound(entityId)) => //print something + } +} \ No newline at end of file diff --git a/tests/patmat/t9657.scala.ignore b/tests/patmat/t9657.scala.ignore new file mode 100644 index 000000000000..d15a441e3237 --- /dev/null +++ b/tests/patmat/t9657.scala.ignore @@ -0,0 +1,28 @@ +sealed trait PowerSource + +case object Petrol extends PowerSource + +case object Pedal extends PowerSource + +sealed abstract class Vehicle { + type A <: PowerSource +} + +case object Bicycle extends Vehicle { + type A = Pedal.type +} + +case class Bus(fuel: Int) extends Vehicle { + type A = Petrol.type +} + +case class Car(fuel: Int) extends Vehicle { + type A = Petrol.type +} + +object Test { + def refuel[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match { + case Car(_) => Car(100) + case Bus(_) => Bus(100) + } +} \ No newline at end of file diff --git a/tests/patmat/t9672.check b/tests/patmat/t9672.check new file mode 100644 index 000000000000..3284d1df1e8d --- /dev/null +++ b/tests/patmat/t9672.check @@ -0,0 +1,5 @@ +./tests/patmat/t9672.scala:22: warning: match may not be exhaustive. +It would fail on the following input: SimpleExpr.IntExpr(_) + def func(expr: Expr) = expr match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/t9672.scala b/tests/patmat/t9672.scala new file mode 100644 index 000000000000..fe068f3d5e78 --- /dev/null +++ b/tests/patmat/t9672.scala @@ -0,0 +1,28 @@ +trait Hierarchy { + sealed trait Expr +} +trait If { + this: Hierarchy => + case class If(cond: Expr, yes: Expr, no: Expr) extends Expr +} +trait Word { + this: Hierarchy => + case class Word(name: String) extends Expr +} +trait IntExpr { + this: Hierarchy => + case class IntExpr(value : Int) extends Expr +} + +object SimpleExpr extends Hierarchy with If with Word with IntExpr +//object OtherExpr extends Hierarchy with If with IntExpr + +object Demo extends App { + import SimpleExpr._ + def func(expr: Expr) = expr match { + case If(cond, yes, no) => cond + case Word(name) => name + // compiler should emit warning "missing case statement" + // emits the wrong warning "unreachable code" + } +} \ No newline at end of file diff --git a/tests/patmat/t9677.scala b/tests/patmat/t9677.scala new file mode 100644 index 000000000000..1e9b1df5e891 --- /dev/null +++ b/tests/patmat/t9677.scala @@ -0,0 +1,23 @@ +sealed abstract class Base + +sealed trait A extends Base + +object A { + + case object Root extends Base + + def apply(param: String): A = { + new A {} + } +} + +object ExhaustiveMatchWarning { + + def test: Unit = { + val b: Base = A("blabla") + b match { + case A.Root => println("Root") + case path: A => println("Not root") + } + } +} \ No newline at end of file From 9a0952cdd0c2bc7a5ed0fdb4bf1cff968d1ab874 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Wed, 25 May 2016 01:25:48 +0200 Subject: [PATCH 02/11] support check java enum from classfile and source file --- src/dotty/tools/dotc/Compiler.scala | 4 - src/dotty/tools/dotc/core/Phases.scala | 8 +- src/dotty/tools/dotc/core/Types.scala | 7 + .../dotc/core/classfile/ClassfileParser.scala | 10 ++ .../tools/dotc/transform/PatternMatcher.scala | 9 +- .../dotc/transform/{ => patmat}/Space.scala | 152 +++++++++--------- src/dotty/tools/dotc/typer/Namer.scala | 30 ++++ .../transform/PatmatExhaustivityTest.scala | 42 ++++- tests/patmat/enum/Day.java | 4 + tests/patmat/enum/expected.check | 5 + tests/patmat/enum/patmat-enum.scala | 9 ++ 11 files changed, 189 insertions(+), 91 deletions(-) rename src/dotty/tools/dotc/transform/{ => patmat}/Space.scala (80%) create mode 100644 tests/patmat/enum/Day.java create mode 100644 tests/patmat/enum/expected.check create mode 100644 tests/patmat/enum/patmat-enum.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index ce9280d827b8..6442d2feec22 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -8,12 +8,8 @@ import Symbols._ import Types._ import Scopes._ import typer.{FrontEnd, Typer, ImportInfo, RefChecks} -import reporting.{Reporter, ConsoleReporter} import Phases.Phase import transform._ -import transform.TreeTransforms.{TreeTransform, TreeTransformer} -import core.DenotTransformers.DenotTransformer -import core.Denotations.SingleDenotation import dotty.tools.backend.jvm.{LabelDefs, GenBCode} import dotty.tools.backend.sjs.GenSJSIR diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 4b2861452d0c..fc7623f94ebc 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -1,17 +1,17 @@ package dotty.tools.dotc package core -import Periods._ import Contexts._ -import dotty.tools.backend.jvm.{LabelDefs, GenBCode} +import dotty.tools.backend.jvm.{GenBCode, LabelDefs} import dotty.tools.dotc.core.Symbols.ClassSymbol import util.DotClass import DenotTransformers._ import Denotations._ import Decorators._ import config.Printers._ -import scala.collection.mutable.{ListBuffer, ArrayBuffer} -import dotty.tools.dotc.transform.TreeTransforms.{TreeTransformer, MiniPhase, TreeTransform} + +import scala.collection.mutable.ListBuffer +import dotty.tools.dotc.transform.TreeTransforms.{MiniPhase, TreeTransformer} import dotty.tools.dotc.transform._ import Periods._ import typer.{FrontEnd, RefChecks} diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index f514a329e1b9..83998303dc27 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -810,6 +810,13 @@ object Types { case _ => this } + /** Eliminate anonymous classes */ + final def elimAnonymousClass(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. */ diff --git a/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index f7a69aa53f28..e159162ccdcc 100644 --- a/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -90,6 +90,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) @@ -145,6 +146,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 } diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9e3047645a34..da417b5cb60f 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -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._ @@ -56,9 +57,9 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans // check exhaustivity and unreachability val engine = new SpaceEngine - if (!engine.skipCheck(sel.tpe)) { - engine.exhaustivity(tree) - engine.redundancy(tree) + if (engine.checkable(sel.tpe.widen.elimAnonymousClass)) { + engine.checkExhaustivity(tree) + engine.checkRedundancy(tree) } translated.ensureConforms(tree.tpe) @@ -1275,7 +1276,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.elimAnonymousClass/*withoutAnnotations*/ val selectorSym = freshSym(sel.pos, selectorTp, "selector") diff --git a/src/dotty/tools/dotc/transform/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala similarity index 80% rename from src/dotty/tools/dotc/transform/Space.scala rename to src/dotty/tools/dotc/transform/patmat/Space.scala index 30a297d0c6bc..c7ddd82d839e 100644 --- a/src/dotty/tools/dotc/transform/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -1,5 +1,6 @@ package dotty.tools.dotc package transform +package patmat import core.Types._ import core.Contexts._ @@ -12,8 +13,26 @@ import core.Symbols._ import core.NameOps._ import core.Constants._ -/** Space logic for checking exhaustivity and unreachability of - * pattern matching. +/** Space logic for checking exhaustivity and unreachability of pattern matching. + * + * The core idea of the algorithm is that patterns and types are value + * spaces, which is recursively defined as follows: + * + * 1. `Empty` is a space + * 2. For a type T, `Typ(T)` is a space + * 3. A union of spaces `S1 | S2 | ...` is a space + * 4. For a case class Kon(x1: T1, x2: T2, .., xn: Tn), if S1, S2, ..., Sn + * are spaces, then `Kon(S1, S2, ..., Sn)` is a space. + * 5. A constant `Const(value, T)` is a point in space + * 6. A stable identifier `Var(sym, T)` is a space + * + * For the problem of exhaustivity check, its formulation in terms of space is as follows: + * + * Is the space Typ(T) a subspace of the union of space covered by all the patterns? + * + * The problem of unreachable patterns can be formulated as follows: + * + * Is the space covered by a pattern a subspace of the space covered by previous patterns? */ @@ -41,7 +60,7 @@ trait SpaceLogic { /** Is the type `tp` decomposable? i.e. all values of the type can be covered * by its decomposed types. * - * Abstract sealed class, OrType and Boolean can be decomposed. + * Abstract sealed class, OrType, Boolean and Java enums can be decomposed. */ def canDecompose(tp: Type): Boolean @@ -197,7 +216,7 @@ trait SpaceLogic { case (Const(_, tp1), Typ(tp2, _)) => if (isSubType(tp1, tp2)) Empty else a case (Const(_, _), _) => a - case (Typ(tp1, _), Const(_, tp2)) => // Boolean + case (Typ(tp1, _), Const(_, tp2)) => // Boolean & Java enum if (isSubType(tp2, tp1) && canDecompose(tp1)) minus(Or(partitions(tp1)), b) else a @@ -215,10 +234,6 @@ trait SpaceLogic { class SpaceEngine(implicit ctx: Context) extends SpaceLogic { import tpd._ - def debug(s: String): Unit = { - if (ctx.debug) println(s) - } - /** Return the space that represents the pattern `pat` * * If roundUp is true, approximate extractors to its type, @@ -227,10 +242,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def project(pat: Tree, roundUp: Boolean = true)(implicit ctx: Context): Space = pat match { case Literal(c) => Const(c, c.tpe) case _: BackquotedIdent => Var(pat.symbol, pat.tpe) - case Ident(_) => Typ(pat.tpe.stripAnnots, false) - case Select(_, _) => + case Ident(_) => + Typ(pat.tpe.stripAnnots, false) + case Select(_, _) => if (pat.symbol.is(Module)) Typ(pat.tpe.stripAnnots, false) + else if (pat.symbol.is(Enum)) + Const(Constant(pat.symbol), pat.tpe) else Var(pat.symbol, pat.tpe) case Alternative(trees) => Or(trees.map(project(_, roundUp))) @@ -243,7 +261,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case Typed(pat @ UnApply(_, _, _), _) => project(pat) case Typed(expr, _) => Typ(expr.tpe.stripAnnots, true) case _ => - debug(s"========unkown tree: $pat========") Empty } @@ -255,71 +272,61 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case (tp1: RefinedType, tp2: RefinedType) => isSubType(tp1.parent, tp2.parent) case (tp1: RefinedType, _) => isSubType(tp1.parent, tp2) case (_, tp2: RefinedType) => isSubType(tp1, tp2.parent) - case (_, _) => - val res = tp1 <:< tp2 - debug(s"$tp1 <: $tp2 ? $res") - res + case (_, _) => tp1 <:< tp2 } - def isEqualType(tp1: Type, tp2: Type): Boolean = { - val res = tp1 =:= tp2 - debug(s"$tp1 == $tp2 ? $res") - res - } + def isEqualType(tp1: Type, tp2: Type): Boolean = tp1 =:= tp2 def signature(tp: Type): List[Type] = { val ktor = tp.classSymbol.primaryConstructor.info - debug(s"=======ktor: $ktor") - val meth = if (ktor.isInstanceOf[MethodType]) ktor else tp match { case AppliedType(_, params) => - debug(s"=======params: $params") val refined = params.map { // TypeBounds would generate an exception case tp: TypeBounds => tp.underlying case tp => tp } - debug(s"=======refined params: $refined") ktor.appliedTo(refined) case _ => ktor } - val sign = meth.firstParamTypes.map(_.stripTypeVar).map(paramTp => refine(tp, paramTp)) - - debug(s"====signature of $tp: $sign") - sign + meth.firstParamTypes.map(_.stripTypeVar).map(refine(tp, _)) } - def partitions(tp: Type): List[Space] = tp match { - case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true)) - case _ if tp =:= ctx.definitions.BooleanType => - List( - Const(Constant(true), ctx.definitions.BooleanType), - Const(Constant(false), ctx.definitions.BooleanType) - ) - case _ => - val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot => - // refer to definition of Annotation.makeChild - val sym = annot.tree match { - case Apply(TypeApply(_, List(tpTree)), _) => tpTree.symbol.asClass - } - - if (sym.is(ModuleClass)) - sym.classInfo.selfType - else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef]) - refine(tp, sym.classInfo.symbolicTypeRef) - else - sym.info - } filter(_ <:< tp) // child may not always be subtype of parent: SI-4020 - - debug(s"=========child of ${tp.show}: ${children.map(_.show).mkString(", ")}") + def partitions(tp: Type): List[Space] = { + val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot => + // refer to definition of Annotation.makeChild + annot.tree match { + case Apply(TypeApply(_, List(tpTree)), _) => tpTree.symbol + } + } - children.map(tp => Typ(tp, true)) + tp match { + case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true)) + case _ if tp =:= ctx.definitions.BooleanType => + List( + Const(Constant(true), ctx.definitions.BooleanType), + Const(Constant(false), ctx.definitions.BooleanType) + ) + case _ if tp.classSymbol.is(Enum) => + children.map(sym => Const(Constant(sym), tp)) + case _ => + val parts = children.map { sym => + if (sym.is(ModuleClass)) + sym.asClass.classInfo.selfType + else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef]) + refine(tp, sym.asClass.classInfo.symbolicTypeRef) + else + sym.info + } filter(_ <:< tp) // child may not always be subtype of parent: SI-4020 + + parts.map(tp => Typ(tp, true)) + } } /** Refine tp2 based on tp1 @@ -337,13 +344,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case _ => tp2 } - /** Abstract sealed types, or-types and Boolean can be decomposed */ - def canDecompose(tp: Type): Boolean = { + /** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */ + def canDecompose(tp: Type): Boolean = tp.typeSymbol.is(allOf(Abstract, Sealed)) || tp.typeSymbol.is(allOf(Trait, Sealed)) || tp.isInstanceOf[OrType] || - tp =:= ctx.definitions.BooleanType - } + tp =:= ctx.definitions.BooleanType || + tp.typeSymbol.is(Enum) def isCaseClass(tp: Type): Boolean = tp.classSymbol.isClass && tp.classSymbol.is(CaseClass) @@ -428,27 +435,24 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { flatten(s).map(doShow(_, false)).distinct.mkString(", ") } - /** Does the type t have @unchecked annotation? */ - def skipCheck(tp: Type): Boolean = tp match { - case AnnotatedType(tp, annot) => ctx.definitions.UncheckedAnnot == annot.symbol - case _ => false - } - - /** Widen a type and eliminate anonymous classes */ - private def widen(tp: Type): Type = tp.widen match { - case tp:TypeRef if tp.symbol.isAnonymousClass => - tp.symbol.asClass.typeRef.asSeenFrom(tp.prefix, tp.symbol.owner) - case tp => tp + def checkable(tp: Type): Boolean = tp match { + case AnnotatedType(tp, annot) => + (ctx.definitions.UncheckedAnnot != annot.symbol) && checkable(tp) + case _ => true // actually everything is checkable unless @unchecked + + // tp.classSymbol.is(Sealed) || + // tp.isInstanceOf[OrType] || + // tp.classSymbol.is(Enum) || + // Boolean + // Int + // ... } - def exhaustivity(_match: Match): Unit = { + def checkExhaustivity(_match: Match): Unit = { val Match(sel, cases) = _match - val selTyp = widen(sel.tpe) + val selTyp = sel.tpe.widen.elimAnonymousClass - debug(s"====patterns:\n${cases.map(_.pat).mkString("\n")}") val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or(List(a, b))) - debug(s"====selector:\n" + selTyp) - debug("====pattern space:\n" + show(patternSpace)) val uncovered = simplify(minus(Typ(selTyp, true), patternSpace)) if (uncovered != Empty) { @@ -460,9 +464,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { } } - def redundancy(_match: Match): Unit = { + def checkRedundancy(_match: Match): Unit = { val Match(sel, cases) = _match - val selTyp = widen(sel.tpe) + val selTyp = sel.tpe.widen.elimAnonymousClass // starts from the second, the first can't be redundant (1 until cases.length).foreach { i => diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index a8f3b89183cf..2b0e360a0c2e 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -408,6 +408,16 @@ class Namer { typer: Typer => case mdef: DefTree => val sym = enterSymbol(createSymbol(mdef)) setDocstring(sym, stat) + + // add java enum constants + mdef match { + case vdef: ValDef if (isEnumConstant(vdef)) => + val enumClass = sym.owner.linkedClass + if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) + enumClass.addAnnotation(Annotation.makeChild(sym)) + case _ => + } + ctx case stats: Thicket => for (tree <- stats.toList) { @@ -419,6 +429,24 @@ class Namer { typer: Typer => ctx } + /** Determines whether this field holds an enum constant. + * To qualify, the following conditions must be met: + * - The field's class has the ENUM flag set + * - The field's class extends java.lang.Enum + * - The field has the ENUM flag set + * - The field is static + * - The field is stable + */ + def isEnumConstant(vd: ValDef)(implicit ctx: Context) = { + // val ownerHasEnumFlag = + // Necessary to check because scalac puts Java's static members into the companion object + // while Scala's enum constants live directly in the class. + // We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal + // cyclic reference error. See the commit message for details. + // if (ctx.compilationUnit.isJava) ctx.owner.companionClass.is(Enum) else ctx.owner.is(Enum) + vd.mods.is(allOf(Enum, Stable, JavaStatic, JavaDefined)) // && ownerHasEnumFlag + } + def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { case t: MemberDef => ctx.base.addDocstring(sym, t.rawComment) case _ => () @@ -996,4 +1024,6 @@ class Namer { typer: Typer => case _ => mapOver(tp) } } + + } diff --git a/test/test/transform/PatmatExhaustivityTest.scala b/test/test/transform/PatmatExhaustivityTest.scala index 1ecaa2b1d14d..37e29fabc268 100644 --- a/test/test/transform/PatmatExhaustivityTest.scala +++ b/test/test/transform/PatmatExhaustivityTest.scala @@ -1,10 +1,10 @@ package test.transform import java.io._ + import scala.io.Source._ import scala.reflect.io.Directory import org.junit.Test - import dotty.tools.dotc.Main import dotty.tools.dotc.reporting.ConsoleReporter @@ -35,13 +35,45 @@ class PatmatExhaustivityTest { (file, checkContent, actual) } - @Test def patmatExhaustivity: Unit = { - val res = Directory(testsDir).deepFiles.toList.filter(_.extension == "scala").map { f => - compileFile(f.jfile) + /** A single test with multiple files grouped in a folder */ + private def compileDir(file: File) = { + val stringBuffer = new StringWriter() + val reporter = new ConsoleReporter(writer = new PrintWriter(stringBuffer)) + + val files = Directory(file.getPath).list.toList + .filter(f => f.extension == "scala" || f.extension == "java" ) + .map(_.jfile.getPath) + + try { + Main.process((options::files).toArray, reporter, null) + } catch { + case e: Throwable => + println(s"Compile $file exception:") + e.printStackTrace() } + val actual = stringBuffer.toString.trim + val checkFilePath = file.getPath + File.separator + "expected.check" + val checkContent = + if (new File(checkFilePath).exists) + fromFile(checkFilePath).getLines.mkString("\n").trim + else "" + + (file, checkContent, actual) + } + + @Test def patmatExhaustivity: Unit = { + val res = Directory(testsDir).list.toList + .filter(f => f.extension == "scala" || f.isDirectory) + .map { f => + if (f.isDirectory) + compileDir(f.jfile) + else + compileFile(f.jfile) + } + val failed = res.filter { case (_, expected, actual) => expected != actual } - val ignored = Directory(testsDir).deepFiles.toList.filter(_.extension == "ignore") + val ignored = Directory(testsDir).list.toList.filter(_.extension == "ignore") failed.foreach { case (file, expected, actual) => println(s"\n----------------- incorrect output for $file --------------\n" + diff --git a/tests/patmat/enum/Day.java b/tests/patmat/enum/Day.java new file mode 100644 index 000000000000..eedb9a72b541 --- /dev/null +++ b/tests/patmat/enum/Day.java @@ -0,0 +1,4 @@ +public enum Day { + SUNDAY, MONDAY, TUESDAY, WEDNESDAY, + THURSDAY, FRIDAY, SATURDAY +} \ No newline at end of file diff --git a/tests/patmat/enum/expected.check b/tests/patmat/enum/expected.check new file mode 100644 index 000000000000..d957e08e104e --- /dev/null +++ b/tests/patmat/enum/expected.check @@ -0,0 +1,5 @@ +./tests/patmat/enum/patmat-enum.scala:4: warning: match may not be exhaustive. +It would fail on the following input: SATURDAY, FRIDAY, THURSDAY, SUNDAY + day match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/enum/patmat-enum.scala b/tests/patmat/enum/patmat-enum.scala new file mode 100644 index 000000000000..e7786b1e28f6 --- /dev/null +++ b/tests/patmat/enum/patmat-enum.scala @@ -0,0 +1,9 @@ +object Test { + val day: Day = ??? + + day match { + case Day.MONDAY => true + case Day.TUESDAY => true + case Day.WEDNESDAY => true + } +} \ No newline at end of file From fd1b28689b14e6ec3627324c1247184841f47e98 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Thu, 2 Jun 2016 20:37:04 +0200 Subject: [PATCH 03/11] fix false warning about Nil scala.Nil refines scala.collection.immutable.Nil, this commit follows redefinition of case objects. --- .../tools/dotc/transform/PatternMatcher.scala | 4 +- .../tools/dotc/transform/patmat/Space.scala | 50 +++++++++++-------- tests/patmat/enum/expected.check | 6 ++- tests/patmat/enum/patmat-enum.scala | 14 +++++- tests/patmat/patmat-adt.scala | 5 ++ tests/patmat/patmat-indent.check | 13 +++++ tests/patmat/patmat-indent.scala | 30 +++++++++++ tests/patmat/t7466.check | 2 +- tests/patmat/t9411a.scala | 27 ++++++++++ tests/patmat/t9411b.scala | 36 +++++++++++++ tests/patmat/virtpatmat_apply.check | 5 ++ tests/patmat/virtpatmat_apply.scala | 7 +++ 12 files changed, 172 insertions(+), 27 deletions(-) create mode 100644 tests/patmat/patmat-indent.check create mode 100644 tests/patmat/patmat-indent.scala create mode 100644 tests/patmat/t9411a.scala create mode 100644 tests/patmat/t9411b.scala create mode 100644 tests/patmat/virtpatmat_apply.check create mode 100644 tests/patmat/virtpatmat_apply.scala diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index da417b5cb60f..af742420c4cb 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -51,13 +51,11 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans private var _id = 0 // left for debuging override def transformMatch(tree: Match)(implicit ctx: Context, info: TransformerInfo): Tree = { - val Match(sel, cases) = tree - val translated = new Translator()(ctx).translator.translateMatch(tree) // check exhaustivity and unreachability val engine = new SpaceEngine - if (engine.checkable(sel.tpe.widen.elimAnonymousClass)) { + if (engine.checkable(tree)) { engine.checkExhaustivity(tree) engine.checkRedundancy(tree) } diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index c7ddd82d839e..0d22e957bc6d 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -116,7 +116,7 @@ trait SpaceLogic { ss.exists(subspace(a, _)) || (canDecompose(tp1) && subspace(Or(partitions(tp1)), b)) case (Typ(tp1, _), Kon(tp2, ss)) => - isSubType(tp1, tp2) && subspace(Kon(tp2, signature(tp2).map(tp => Typ(tp, false))), b) + isSubType(tp1, tp2) && subspace(Kon(tp2, signature(tp2).map(Typ(_, false))), b) case (Kon(tp1, ss), Typ(tp2, _)) => isSubType(tp1, tp2) || simplify(a) == Empty || @@ -188,7 +188,7 @@ trait SpaceLogic { minus(Or(partitions(tp1)), b) else a case (Typ(tp1, _), Kon(tp2, ss)) => - if (isSubType(tp1, tp2)) minus(Kon(tp2, signature(tp2).map(tp => Typ(tp, false))), b) + if (isSubType(tp1, tp2)) minus(Kon(tp2, signature(tp2).map(Typ(_, false))), b) else if (isSubType(tp2, tp1) && canDecompose(tp1)) minus(Or(partitions(tp1)), b) else a @@ -242,15 +242,17 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def project(pat: Tree, roundUp: Boolean = true)(implicit ctx: Context): Space = pat match { case Literal(c) => Const(c, c.tpe) case _: BackquotedIdent => Var(pat.symbol, pat.tpe) - case Ident(_) => - Typ(pat.tpe.stripAnnots, false) - case Select(_, _) => - if (pat.symbol.is(Module)) - Typ(pat.tpe.stripAnnots, false) - else if (pat.symbol.is(Enum)) - Const(Constant(pat.symbol), pat.tpe) - else - Var(pat.symbol, pat.tpe) + case Ident(_) | Select(_, _) => + pat.tpe.stripAnnots match { + case tp: TermRef => + if (pat.symbol.is(Enum)) + Const(Constant(pat.symbol), tp) + else if (tp.underlyingIterator.exists(_.classSymbol.is(Module))) + Typ(tp.widenTermRefExpr.stripAnnots, false) + else + Var(pat.symbol, tp) + case tp => Typ(tp, false) + } case Alternative(trees) => Or(trees.map(project(_, roundUp))) case Bind(_, pat) => project(pat) case UnApply(_, _, pats) => @@ -295,6 +297,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { ktor } + // refine path-dependent type in params. refer to t9672 meth.firstParamTypes.map(_.stripTypeVar).map(refine(tp, _)) } @@ -415,19 +418,19 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { if (mergeList) "_" else "List(_)" else if (tp.classSymbol.is(CaseClass)) // use constructor syntax for case class - showType(tp) + signature(tp).map(_ => "_").mkString("(", ",", ")") + showType(tp) + signature(tp).map(_ => "_").mkString("(", ", ", ")") else if (signature(tp).nonEmpty) - tp.classSymbol.name + signature(tp).map(_ => "_").mkString("(", ",", ")") + tp.classSymbol.name + signature(tp).map(_ => "_").mkString("(", ", ", ")") else if (decomposed) "_: " + showType(tp) else "_" case Kon(tp, params) => if (ctx.definitions.isTupleType(tp)) - "(" + params.map(p => doShow(p)).mkString(", ") + ")" + "(" + params.map(doShow(_)).mkString(", ") + ")" else if (tp.widen.classSymbol.showFullName == "scala.collection.immutable.::") - if (mergeList) params.map(p => doShow(p, mergeList)).mkString(", ") - else params.map(p => doShow(p, true)).mkString("List(", ", ", ")") + if (mergeList) params.map(doShow(_, mergeList)).mkString(", ") + else params.map(doShow(_, true)).filter(_ != "Nil").mkString("List(", ", ", ")") else - showType(tp) + params.map(p => doShow(p)).mkString("(", ", ", ")") + showType(tp) + params.map(doShow(_)).mkString("(", ", ", ")") case Or(_) => throw new Exception("incorrect flatten result " + s) } @@ -435,10 +438,11 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { flatten(s).map(doShow(_, false)).distinct.mkString(", ") } - def checkable(tp: Type): Boolean = tp match { - case AnnotatedType(tp, annot) => - (ctx.definitions.UncheckedAnnot != annot.symbol) && checkable(tp) - case _ => true // actually everything is checkable unless @unchecked + def checkable(tree: Match): Boolean = { + def isCheckable(tp: Type): Boolean = tp match { + case AnnotatedType(tp, annot) => + (ctx.definitions.UncheckedAnnot != annot.symbol) && isCheckable(tp) + case _ => true // actually everything is checkable unless @unchecked // tp.classSymbol.is(Sealed) || // tp.isInstanceOf[OrType] || @@ -446,6 +450,10 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { // Boolean // Int // ... + } + + val Match(sel, cases) = tree + isCheckable(sel.tpe.widen.elimAnonymousClass) } def checkExhaustivity(_match: Match): Unit = { diff --git a/tests/patmat/enum/expected.check b/tests/patmat/enum/expected.check index d957e08e104e..b3dafa8bdd02 100644 --- a/tests/patmat/enum/expected.check +++ b/tests/patmat/enum/expected.check @@ -2,4 +2,8 @@ It would fail on the following input: SATURDAY, FRIDAY, THURSDAY, SUNDAY day match { ^ -one warning found \ No newline at end of file +./tests/patmat/enum/patmat-enum.scala:15: warning: match may not be exhaustive. +It would fail on the following input: SATURDAY, FRIDAY, THURSDAY + day match { + ^ +two warnings found \ No newline at end of file diff --git a/tests/patmat/enum/patmat-enum.scala b/tests/patmat/enum/patmat-enum.scala index e7786b1e28f6..ec5c90255f7a 100644 --- a/tests/patmat/enum/patmat-enum.scala +++ b/tests/patmat/enum/patmat-enum.scala @@ -1,4 +1,4 @@ -object Test { +object Test1 { val day: Day = ??? day match { @@ -6,4 +6,16 @@ object Test { case Day.TUESDAY => true case Day.WEDNESDAY => true } +} + +object Test2 { + import Day._ + val day: Day = ??? + + day match { + case MONDAY => true + case TUESDAY => true + case WEDNESDAY => true + case SUNDAY => true + } } \ No newline at end of file diff --git a/tests/patmat/patmat-adt.scala b/tests/patmat/patmat-adt.scala index acb4d3465ee6..e7eac4e4a662 100644 --- a/tests/patmat/patmat-adt.scala +++ b/tests/patmat/patmat-adt.scala @@ -50,4 +50,9 @@ object PatmatADT { def foo5(tree: Tree) : Any = tree match { case LetL(CharLit) => } + + def foo6[T](l: List[T]): Boolean = l match { + case x::xs => true + case Nil => false + } } \ No newline at end of file diff --git a/tests/patmat/patmat-indent.check b/tests/patmat/patmat-indent.check new file mode 100644 index 000000000000..3a76e0a9581b --- /dev/null +++ b/tests/patmat/patmat-indent.check @@ -0,0 +1,13 @@ +./tests/patmat/patmat-indent.scala:9: warning: match may not be exhaustive. +It would fail on the following input: Nil + def foo1a[T](l: List[T]) = l match { + ^ +./tests/patmat/patmat-indent.scala:23: warning: match may not be exhaustive. +It would fail on the following input: _: Boolean + def foo2(b: Boolean) = b match { + ^ +./tests/patmat/patmat-indent.scala:27: warning: match may not be exhaustive. +It would fail on the following input: _: Int + def foo3(x: Int) = x match { + ^ +three warnings found \ No newline at end of file diff --git a/tests/patmat/patmat-indent.scala b/tests/patmat/patmat-indent.scala new file mode 100644 index 000000000000..ef25bb2c7ccf --- /dev/null +++ b/tests/patmat/patmat-indent.scala @@ -0,0 +1,30 @@ +object Test { + val Nil = scala.Nil + val X = 5 + + object Inner { + val Y = false + } + + def foo1a[T](l: List[T]) = l match { + case x::xs => false + } + + def foo1b[T](l: List[T]) = l match { + case Nil => true + case x::xs => false + } + + def foo1c[T](l: List[T]) = l match { + case Test.Nil => true + case x::xs => false + } + + def foo2(b: Boolean) = b match { + case Inner.Y => false + } + + def foo3(x: Int) = x match { + case X => 0 + } +} \ No newline at end of file diff --git a/tests/patmat/t7466.check b/tests/patmat/t7466.check index 93f7fd62a072..8e575f6a213f 100644 --- a/tests/patmat/t7466.check +++ b/tests/patmat/t7466.check @@ -1,5 +1,5 @@ ./tests/patmat/t7466.scala:8: warning: match may not be exhaustive. -It would fail on the following input: (true, _), (false, _), (_, true), (_, false) +It would fail on the following input: (_, _) (b1, b2) match { ^ one warning found \ No newline at end of file diff --git a/tests/patmat/t9411a.scala b/tests/patmat/t9411a.scala new file mode 100644 index 000000000000..d5264663ece9 --- /dev/null +++ b/tests/patmat/t9411a.scala @@ -0,0 +1,27 @@ +object OhNoes { + + sealed trait F + sealed abstract class FA extends F + sealed abstract class FB extends F + + case object FA1 extends FA + case object FB1 extends FB + case object FB2 extends FB + + sealed trait G + case object G1 extends G + case object G2 extends G + + sealed trait H + case class H1(a: FB, b: G) extends H + case class H2(a: F) extends H + + val demo: H => Unit = { + case H1(FB1, G1) => + case H1(FB2, G2) => + case H2(_: FB) => + case H2(_: FA) => + case H1(FB1, G2) => + case H1(FB2, G1) => + } +} diff --git a/tests/patmat/t9411b.scala b/tests/patmat/t9411b.scala new file mode 100644 index 000000000000..6888ba9382c8 --- /dev/null +++ b/tests/patmat/t9411b.scala @@ -0,0 +1,36 @@ +object OhNoes { + + sealed trait F + sealed abstract class FA extends F + sealed abstract class FB extends F + + case object FA1 extends FA + case object FB1 extends FB + case object FB2 extends FB + + sealed trait G + case object G1 extends G + case object G2 extends G + + sealed trait H + case class H1(a: FB, b: G) extends H + case class H2(b: F) extends H + + val demo: H => Unit = { + case H1(FB1, G1) => + case H1(FB2, G2) => + case H2(_: FB) => + case H2(_: FA) => + case H1(FB1, G2) => + case H1(FB2, G1) => + } + + val demo2: H => Unit = { + case H2(_: FA) => + case H2(_: FB) => + case H1(FB1, G1) => + case H1(FB2, G1) => + case H1(FB1, G2) => + case H1(FB2, G2) => + } +} diff --git a/tests/patmat/virtpatmat_apply.check b/tests/patmat/virtpatmat_apply.check new file mode 100644 index 000000000000..d10d82165a3e --- /dev/null +++ b/tests/patmat/virtpatmat_apply.check @@ -0,0 +1,5 @@ +./tests/patmat/virtpatmat_apply.scala:2: warning: match may not be exhaustive. +It would fail on the following input: List(_) + List(1, 2, 3) match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/virtpatmat_apply.scala b/tests/patmat/virtpatmat_apply.scala new file mode 100644 index 000000000000..646d15f902ac --- /dev/null +++ b/tests/patmat/virtpatmat_apply.scala @@ -0,0 +1,7 @@ +object Test { + List(1, 2, 3) match { + case Nil => println("FAIL") + case x :: y :: xs if xs.length == 2 => println("FAIL") + case x :: y :: xs if xs.length == 1 => println("OK "+ y) + } +} From d681a78c554709f6893f6ff3512434534bbcbb41 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 3 Jun 2016 12:02:46 +0200 Subject: [PATCH 04/11] suppress checking of for, try and partial functions --- src/dotty/tools/dotc/ast/Desugar.scala | 16 ++++++++++------ src/dotty/tools/dotc/transform/ExpandSAMs.scala | 3 ++- .../tools/dotc/transform/patmat/Space.scala | 6 +++--- src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 3 ++- test/test/transform/PatmatExhaustivityTest.scala | 8 ++++---- tests/patmat/for.scala | 5 +++++ tests/patmat/i947.check | 4 ++++ tests/patmat/i947.scala | 16 ++++++++++++++++ tests/patmat/partial-function.scala | 12 ++++++++++++ tests/patmat/t1056.scala | 5 +++++ tests/patmat/try.scala | 5 +++++ 12 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 tests/patmat/for.scala create mode 100644 tests/patmat/i947.check create mode 100644 tests/patmat/i947.scala create mode 100644 tests/patmat/partial-function.scala create mode 100644 tests/patmat/t1056.scala create mode 100644 tests/patmat/try.scala diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index f603f68178a3..12ca8c14cea4 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -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: @@ -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 @@ -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`? diff --git a/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/src/dotty/tools/dotc/transform/ExpandSAMs.scala index d9445d04673e..04c6864b1cd2 100644 --- a/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -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 } diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index 0d22e957bc6d..08896fb3364c 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -258,7 +258,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case UnApply(_, _, pats) => if (pat.tpe.classSymbol.is(CaseClass)) Kon(pat.tpe.stripAnnots, pats.map(pat => project(pat, roundUp))) - else if (roundUp) Typ(pat.tpe, false) + else if (roundUp) Typ(pat.tpe.stripAnnots, false) else Empty case Typed(pat @ UnApply(_, _, _), _) => project(pat) case Typed(expr, _) => Typ(expr.tpe.stripAnnots, true) @@ -392,9 +392,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case _ => tp.show.stripSuffix("$") } - val text = tp match { + val text = tp.stripAnnots match { case tp: OrType => showType(tp.tp1) + " | " + showType(tp.tp2) - case _ => refine(tp) + case tp => refine(tp) } if (text.isEmpty) enclosingCls.show.stripSuffix("$") diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 995fa43ca7b1..b2abe3b36f49 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -470,7 +470,7 @@ trait TypeAssigner { tree.withType(sym.nonMemberTermRef) def assignType(tree: untpd.Annotated, annot: Tree, arg: Tree)(implicit ctx: Context) = - tree.withType(AnnotatedType(arg.tpe, Annotation(annot))) + tree.withType(AnnotatedType(arg.tpe.widen, Annotation(annot))) def assignType(tree: untpd.PackageDef, pid: Tree)(implicit ctx: Context) = tree.withType(pid.symbol.valRef) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 268020ec51d3..3834a57c2599 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -719,7 +719,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree.selector match { case EmptyTree => val (protoFormals, _) = decomposeProtoFunction(pt, 1) - typed(desugar.makeCaseLambda(tree.cases, protoFormals.length) withPos tree.pos, pt) + val unchecked = pt <:< defn.PartialFunctionType + typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) case _ => val sel1 = typedExpr(tree.selector) val selType = widenForMatchSelector( diff --git a/test/test/transform/PatmatExhaustivityTest.scala b/test/test/transform/PatmatExhaustivityTest.scala index 37e29fabc268..935085480f5e 100644 --- a/test/test/transform/PatmatExhaustivityTest.scala +++ b/test/test/transform/PatmatExhaustivityTest.scala @@ -10,15 +10,15 @@ import dotty.tools.dotc.reporting.ConsoleReporter class PatmatExhaustivityTest { val testsDir = "./tests/patmat" - val options = "-Ystop-after:splitter" // no need for code generation - // patmatexhaust-huge.scala crash compiler + // stop-after: patmatexhaust-huge.scala crash compiler + val options = List("-Ystop-after:splitter") private def compileFile(file: File) = { val stringBuffer = new StringWriter() val reporter = new ConsoleReporter(writer = new PrintWriter(stringBuffer)) try { - Main.process(Array(file.getPath, options), reporter, null) + Main.process((file.getPath::options).toArray, reporter, null) } catch { case e: Throwable => println(s"Compile $file exception:") @@ -45,7 +45,7 @@ class PatmatExhaustivityTest { .map(_.jfile.getPath) try { - Main.process((options::files).toArray, reporter, null) + Main.process((options ++ files).toArray, reporter, null) } catch { case e: Throwable => println(s"Compile $file exception:") diff --git a/tests/patmat/for.scala b/tests/patmat/for.scala new file mode 100644 index 000000000000..ae9dcf65e9f1 --- /dev/null +++ b/tests/patmat/for.scala @@ -0,0 +1,5 @@ +object Test { + def foo[A, B](l: List[(A, B)]): List[A] = { + for ((a, b) <- l) yield a + } +} \ No newline at end of file diff --git a/tests/patmat/i947.check b/tests/patmat/i947.check new file mode 100644 index 000000000000..5cce559c4834 --- /dev/null +++ b/tests/patmat/i947.check @@ -0,0 +1,4 @@ +./tests/patmat/i947.scala:10: warning: unreachable code + case ys: List[d18383] => false + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/i947.scala b/tests/patmat/i947.scala new file mode 100644 index 000000000000..0f2d9e77583a --- /dev/null +++ b/tests/patmat/i947.scala @@ -0,0 +1,16 @@ +object Test { + + class c { + + private var x: Int = 0 + + override def equals(other: Any) = other match { + case o: c => x == o.x + case xs: List[c] => false + case ys: List[d18383] => false + case _ => false + } + + + } +} diff --git a/tests/patmat/partial-function.scala b/tests/patmat/partial-function.scala new file mode 100644 index 000000000000..f168489dad74 --- /dev/null +++ b/tests/patmat/partial-function.scala @@ -0,0 +1,12 @@ +sealed abstract class TA +sealed abstract class TB extends TA +case object B extends TB +case object B2 extends TB + +case class CC(i: Int, tb: TB) + +object Test { + def foo: PartialFunction[CC, Unit] = { + case CC(_, B) => () + } +} \ No newline at end of file diff --git a/tests/patmat/t1056.scala b/tests/patmat/t1056.scala new file mode 100644 index 000000000000..68f1ff27310c --- /dev/null +++ b/tests/patmat/t1056.scala @@ -0,0 +1,5 @@ +object Test { + type T = PartialFunction[String,String] + def g(h: T) = () + g({case s: String => s}) +} diff --git a/tests/patmat/try.scala b/tests/patmat/try.scala new file mode 100644 index 000000000000..d7df24ee0184 --- /dev/null +++ b/tests/patmat/try.scala @@ -0,0 +1,5 @@ +object Test { + try 2/0 catch { + case e: Exception => + } +} \ No newline at end of file From da9511d599cd1b110de159aa2204062d3e1b59a4 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Sun, 12 Jun 2016 00:02:05 +0200 Subject: [PATCH 05/11] add GADT test --- .../tools/dotc/transform/patmat/Space.scala | 25 +++++----- tests/patmat/gadt.check | 13 +++++ tests/patmat/gadt.scala | 49 +++++++++++++++++++ 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 tests/patmat/gadt.check create mode 100644 tests/patmat/gadt.scala diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index 08896fb3364c..5c4e2fd370b7 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -266,19 +266,12 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { Empty } - /** Is `tp1` a subtype of `tp2`? - * - * Ignore type parameters in comparison due to erasure, i.e., Some[Int] <: Some[T] - */ - def isSubType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match { - case (tp1: RefinedType, tp2: RefinedType) => isSubType(tp1.parent, tp2.parent) - case (tp1: RefinedType, _) => isSubType(tp1.parent, tp2) - case (_, tp2: RefinedType) => isSubType(tp1, tp2.parent) - case (_, _) => tp1 <:< tp2 - } + /** Is `tp1` a subtype of `tp2`? */ + def isSubType(tp1: Type, tp2: Type): Boolean = tp1 <:< tp2 def isEqualType(tp1: Type, tp2: Type): Boolean = tp1 =:= tp2 + /** Parameter types of the case class type `tp` */ def signature(tp: Type): List[Type] = { val ktor = tp.classSymbol.primaryConstructor.info @@ -325,10 +318,14 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef]) refine(tp, sym.asClass.classInfo.symbolicTypeRef) else - sym.info - } filter(_ <:< tp) // child may not always be subtype of parent: SI-4020 - - parts.map(tp => Typ(tp, true)) + sym.typeRef + } filter { tpe => + // Child class may not always be subtype of parent: + // GADT & path-dependent types + isSubType(tpe, tp) + } + + parts.map(Typ(_, true)) } } diff --git a/tests/patmat/gadt.check b/tests/patmat/gadt.check new file mode 100644 index 000000000000..caeb0c4eb39e --- /dev/null +++ b/tests/patmat/gadt.check @@ -0,0 +1,13 @@ +./tests/patmat/gadt.scala:13: warning: match may not be exhaustive. +It would fail on the following input: IntLit(_) + def foo1b(x: Expr[Int]) = x match { + ^ +./tests/patmat/gadt.scala:22: warning: match may not be exhaustive. +It would fail on the following input: Or(_, _) + def foo2b(x: Expr[Boolean]) = x match { + ^ +./tests/patmat/gadt.scala:45: warning: match may not be exhaustive. +It would fail on the following input: BooleanLit(_), IntLit(_) + def foo4b(x: Expr) = x match { + ^ +three warnings found \ No newline at end of file diff --git a/tests/patmat/gadt.scala b/tests/patmat/gadt.scala new file mode 100644 index 000000000000..54751d5d8800 --- /dev/null +++ b/tests/patmat/gadt.scala @@ -0,0 +1,49 @@ +object Test { + sealed trait Expr[T] + case class IntLit(i: Int) extends Expr[Int] + case class BooleanLit(b: Boolean) extends Expr[Boolean] + case class Sum(l: Expr[Int], r: Expr[Int]) extends Expr[Int] + case class Or(l: Expr[Boolean], r: Expr[Boolean]) extends Expr[Boolean] + + def foo1a(x: Expr[Int]) = x match { + case _: IntLit => true + case _: Sum => true + } + + def foo1b(x: Expr[Int]) = x match { + case _: Sum => true + } + + def foo2a(x: Expr[Boolean]) = x match { + case _: BooleanLit => true + case _: Or => true + } + + def foo2b(x: Expr[Boolean]) = x match { + case _: BooleanLit => true + } + + def foo3a(x: Expr[Boolean]) = x match { + case _: BooleanLit => true + case _: Or => true + // case _: Sum => true + } + + def foo3b(x: Expr[Int]) = x match { + case _: IntLit => true + case _: Sum => true + // case _: Or => true + } + + def foo4a(x: Expr) = x match { + case _: IntLit => true + case _: Sum => true + case _: BooleanLit => true + case _: Or => true + } + + def foo4b(x: Expr) = x match { + case _: Sum => true + case _: Or => true + } +} \ No newline at end of file From 89e8ff917826529890b10aa157634cbe4c15e4ef Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Sun, 12 Jun 2016 00:34:58 +0200 Subject: [PATCH 06/11] refine patmat check for type erasure --- src/dotty/tools/dotc/transform/patmat/Space.scala | 15 +++++++++++++-- tests/patmat/t2425.scala | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/patmat/t2425.scala diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index 5c4e2fd370b7..4c57416c631d 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -251,7 +251,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { Typ(tp.widenTermRefExpr.stripAnnots, false) else Var(pat.symbol, tp) - case tp => Typ(tp, false) + case tp => Typ(erase(tp), false) } case Alternative(trees) => Or(trees.map(project(_, roundUp))) case Bind(_, pat) => project(pat) @@ -261,11 +261,22 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { else if (roundUp) Typ(pat.tpe.stripAnnots, false) else Empty case Typed(pat @ UnApply(_, _, _), _) => project(pat) - case Typed(expr, _) => Typ(expr.tpe.stripAnnots, true) + case Typed(expr, _) => Typ(erase(expr.tpe.stripAnnots), true) case _ => Empty } + /* Erase a type binding according to erasure rule */ + def erase(tp: Type): Type = { + def doErase(tp: Type): Type = tp match { + case tp: RefinedType => erase(tp.parent) + case _ => tp + } + + val origin = doErase(tp) + if (origin =:= defn.ArrayType) tp else origin + } + /** Is `tp1` a subtype of `tp2`? */ def isSubType(tp1: Type, tp2: Type): Boolean = tp1 <:< tp2 diff --git a/tests/patmat/t2425.scala b/tests/patmat/t2425.scala new file mode 100644 index 000000000000..477d5467aab3 --- /dev/null +++ b/tests/patmat/t2425.scala @@ -0,0 +1,15 @@ +trait B +class D extends B +object Test extends App { + def foo[T](bar: T) = { + bar match { + case _: Array[Array[_]] => println("array 2d") + case _: Array[_] => println("array 1d") + case _ => println("something else") + } + } + foo(Array.fill(10)(2)) + foo(Array.fill(10, 10)(2)) + foo(Array.fill(10, 10, 10)(2)) + foo(List(1, 2, 3)) +} From b25e9526cbc43e5a5d7c5c07715d157a5bc0ac82 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Sun, 12 Jun 2016 02:07:06 +0200 Subject: [PATCH 07/11] add tuple test to patmat check --- src/dotty/tools/dotc/transform/patmat/Space.scala | 8 +++++++- tests/patmat/tuple.scala | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/patmat/tuple.scala diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index 4c57416c631d..031f3518c5bf 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -302,7 +302,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { } // refine path-dependent type in params. refer to t9672 - meth.firstParamTypes.map(_.stripTypeVar).map(refine(tp, _)) + meth.firstParamTypes.map(_.stripTypeVar).map { ptp => + (tp, ptp) match { + case (TypeRef(ref1: TypeProxy, _), TypeRef(ref2: TypeProxy, name)) => + if (ref1.underlying <:< ref2.underlying) TypeRef(ref1, name) else ptp + case _ => ptp + } + } } def partitions(tp: Type): List[Space] = { diff --git a/tests/patmat/tuple.scala b/tests/patmat/tuple.scala new file mode 100644 index 000000000000..f33a5cfec38f --- /dev/null +++ b/tests/patmat/tuple.scala @@ -0,0 +1,5 @@ +object Test { + (4, (4, 6)) match { + case (x, (y, z)) => true + } +} \ No newline at end of file From e2db834f2e7a472da119629a509ca4433fccd3d2 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Mon, 13 Jun 2016 15:42:44 +0200 Subject: [PATCH 08/11] add test t5440 for patmat check --- tests/patmat/t5440.check | 8 ++++---- tests/patmat/t5440.scala | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 tests/patmat/t5440.scala diff --git a/tests/patmat/t5440.check b/tests/patmat/t5440.check index f7d97eef78f3..0780d652945e 100644 --- a/tests/patmat/t5440.check +++ b/tests/patmat/t5440.check @@ -1,5 +1,5 @@ -./tests/patmat/t5440.scala:3: warning: match may not be exhaustive. -It would fail on the following input: { (Nil, List(_)), (List(_), Nil) } - (list1, list2) match { - ^ +./tests/patmat/t5440.scala:2: warning: match may not be exhaustive. +It would fail on the following input: (Nil, List(_)), (List(_), Nil) + def merge(list1: List[Long], list2: List[Long]): Boolean = (list1, list2) match { + ^ one warning found diff --git a/tests/patmat/t5440.scala b/tests/patmat/t5440.scala new file mode 100644 index 000000000000..6721b0562466 --- /dev/null +++ b/tests/patmat/t5440.scala @@ -0,0 +1,6 @@ +object Test { + def merge(list1: List[Long], list2: List[Long]): Boolean = (list1, list2) match { + case (hd1::_, hd2::_) => true + case (Nil, Nil) => true + } +} \ No newline at end of file From 2d6e80348ad06a3bb60e9b314e5a257f74f36186 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Wed, 15 Jun 2016 14:18:05 +0200 Subject: [PATCH 09/11] better warning for higher-kinded types and alias types --- src/dotty/tools/dotc/transform/patmat/Space.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index 031f3518c5bf..7c5fc3df7af1 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -10,6 +10,7 @@ import ast.tpd import core.Decorators._ import core.TypeApplications._ import core.Symbols._ +import core.StdNames._ import core.NameOps._ import core.Constants._ @@ -401,7 +402,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case tp: ThisType => refine(tp.tref) case tp: NamedType => val pre = refinePrefix(tp.prefix) - if (pre.isEmpty) tp.name.show.stripSuffix("$") + if (tp.name == tpnme.hkApply) pre + else if (pre.isEmpty) tp.name.show.stripSuffix("$") else pre + "." + tp.name.show.stripSuffix("$") case _ => tp.show.stripSuffix("$") } @@ -472,7 +474,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def checkExhaustivity(_match: Match): Unit = { val Match(sel, cases) = _match - val selTyp = sel.tpe.widen.elimAnonymousClass + val selTyp = sel.tpe.widen.elimAnonymousClass.dealias + val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or(List(a, b))) val uncovered = simplify(minus(Typ(selTyp, true), patternSpace)) From f69b305e56cac0836baaed88982660fe40070aed Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Thu, 23 Jun 2016 10:12:22 +0200 Subject: [PATCH 10/11] fix SI-9657 about type bounds --- .../tools/dotc/transform/patmat/Space.scala | 72 +++++++++++++++++-- tests/patmat/gadt.check | 6 +- tests/patmat/gadt.scala | 11 ++- tests/patmat/t9657.check | 17 +++++ tests/patmat/t9657.scala | 62 ++++++++++++++++ tests/patmat/t9657.scala.ignore | 28 -------- 6 files changed, 161 insertions(+), 35 deletions(-) create mode 100644 tests/patmat/t9657.check create mode 100644 tests/patmat/t9657.scala delete mode 100644 tests/patmat/t9657.scala.ignore diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index 7c5fc3df7af1..6d5323d08373 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -14,10 +14,14 @@ import core.StdNames._ import core.NameOps._ import core.Constants._ -/** Space logic for checking exhaustivity and unreachability of pattern matching. +/** Space logic for checking exhaustivity and unreachability of pattern matching * - * The core idea of the algorithm is that patterns and types are value - * spaces, which is recursively defined as follows: + * Space can be thought of as a set of possible values. A type or a pattern + * both refer to spaces. The space of a type is the values that inhabit the + * type. The space of a pattern is the values that can be covered by the + * pattern. + * + * Space is recursively defined as follows: * * 1. `Empty` is a space * 2. For a type T, `Typ(T)` is a space @@ -279,7 +283,11 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { } /** Is `tp1` a subtype of `tp2`? */ - def isSubType(tp1: Type, tp2: Type): Boolean = tp1 <:< tp2 + def isSubType(tp1: Type, tp2: Type): Boolean = { + // expose is important due to type bounds + // check SI-9657 and tests/patmat/gadt.scala + tp1 <:< expose(tp2) + } def isEqualType(tp1: Type, tp2: Type): Boolean = tp1 =:= tp2 @@ -472,6 +480,59 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { isCheckable(sel.tpe.widen.elimAnonymousClass) } + + /** Expose refined type to eliminate reference to type variables + * + * A = B M { type T = A } ~~> M { type T = B } + * + * A <: X :> Y M { type T = A } ~~> M { type T <: X :> Y } + * + * A <: X :> Y B <: U :> V M { type T <: A :> B } ~~> M { type T <: X :> B } + * + * A = X B = Y M { type T <: A :> B } ~~> M { type T <: X :> Y } + */ + def expose(tp: Type): Type = { + def follow(tp: Type, up: Boolean): Type = tp match { + case tp: TypeProxy => + tp.underlying match { + case TypeBounds(lo, hi) => + follow(if (up) hi else lo, up) + case _ => + tp + } + case OrType(tp1, tp2) => + OrType(follow(tp1, up), follow(tp2, up)) + case AndType(tp1, tp2) => + AndType(follow(tp1, up), follow(tp2, up)) + } + + tp match { + case tp: RefinedType => + tp.refinedInfo match { + case tpa : TypeAlias => + val hi = follow(tpa.alias, true) + val lo = follow(tpa.alias, false) + val refined = if (hi =:= lo) + tpa.derivedTypeAlias(hi) + else + tpa.derivedTypeBounds(lo, hi) + + tp.derivedRefinedType( + expose(tp.parent), + tp.refinedName, + refined + ) + case tpb @ TypeBounds(lo, hi) => + tp.derivedRefinedType( + expose(tp.parent), + tp.refinedName, + tpb.derivedTypeBounds(follow(lo, false), follow(hi, true)) + ) + } + case _ => tp + } + } + def checkExhaustivity(_match: Match): Unit = { val Match(sel, cases) = _match val selTyp = sel.tpe.widen.elimAnonymousClass.dealias @@ -491,7 +552,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def checkRedundancy(_match: Match): Unit = { val Match(sel, cases) = _match - val selTyp = sel.tpe.widen.elimAnonymousClass + // ignore selector type for now + // val selTyp = sel.tpe.widen.elimAnonymousClass.dealias // starts from the second, the first can't be redundant (1 until cases.length).foreach { i => diff --git a/tests/patmat/gadt.check b/tests/patmat/gadt.check index caeb0c4eb39e..79b3494e08cf 100644 --- a/tests/patmat/gadt.check +++ b/tests/patmat/gadt.check @@ -10,4 +10,8 @@ It would fail on the following input: Or(_, _) It would fail on the following input: BooleanLit(_), IntLit(_) def foo4b(x: Expr) = x match { ^ -three warnings found \ No newline at end of file +./tests/patmat/gadt.scala:55: warning: match may not be exhaustive. +It would fail on the following input: Sum(_, _) + def foo5b[T <: Int](x: Expr[T]) = x match { + ^ +four warnings found \ No newline at end of file diff --git a/tests/patmat/gadt.scala b/tests/patmat/gadt.scala index 54751d5d8800..9f3e7696f75d 100644 --- a/tests/patmat/gadt.scala +++ b/tests/patmat/gadt.scala @@ -46,4 +46,13 @@ object Test { case _: Sum => true case _: Or => true } -} \ No newline at end of file + + def foo5a[T <: Int](x: Expr[T]) = x match { + case _: IntLit => true + case _: Sum => true + } + + def foo5b[T <: Int](x: Expr[T]) = x match { + case _: IntLit => true + } +} diff --git a/tests/patmat/t9657.check b/tests/patmat/t9657.check new file mode 100644 index 000000000000..d3e2ec73f494 --- /dev/null +++ b/tests/patmat/t9657.check @@ -0,0 +1,17 @@ +./tests/patmat/t9657.scala:29: warning: match may not be exhaustive. +It would fail on the following input: Bus(_) + def refuel2[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match { + ^ +./tests/patmat/t9657.scala:38: warning: match may not be exhaustive. +It would fail on the following input: Bus(_) + def foo2(vehicle: Vehicle {type A <: Petrol.type} ): Vehicle = vehicle match { + ^ +./tests/patmat/t9657.scala:49: warning: match may not be exhaustive. +It would fail on the following input: Bus(_) + def bar2(vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match { + ^ +./tests/patmat/t9657.scala:58: warning: match may not be exhaustive. +It would fail on the following input: Bus(_) + def qux2[P <: Petrol.type](vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match { + ^ +four warnings found \ No newline at end of file diff --git a/tests/patmat/t9657.scala b/tests/patmat/t9657.scala new file mode 100644 index 000000000000..f9769574ed35 --- /dev/null +++ b/tests/patmat/t9657.scala @@ -0,0 +1,62 @@ +sealed trait PowerSource + +case object Petrol extends PowerSource + +case object Pedal extends PowerSource + +sealed abstract class Vehicle { + type A <: PowerSource +} + +case object Bicycle extends Vehicle { + type A = Pedal.type +} + +case class Bus(fuel: Int) extends Vehicle { + type A = Petrol.type +} + +case class Car(fuel: Int) extends Vehicle { + type A = Petrol.type +} + +class Test { + def refuel[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match { + case Car(_) => Car(100) + case Bus(_) => Bus(100) + } + + def refuel2[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match { + case Car(_) => Car(100) + } + + def foo1(vehicle: Vehicle {type A <: Petrol.type} ): Vehicle = vehicle match { + case Car(_) => Car(100) + case Bus(_) => Bus(100) + } + + def foo2(vehicle: Vehicle {type A <: Petrol.type} ): Vehicle = vehicle match { + case Car(_) => Car(100) + } + + type P = Petrol.type + + def bar1(vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match { + case Car(_) => Car(100) + case Bus(_) => Bus(100) + } + + def bar2(vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match { + case Car(_) => Car(100) + } + + def qux1[P <: Petrol.type](vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match { + case Car(_) => Car(100) + case Bus(_) => Bus(100) + } + + def qux2[P <: Petrol.type](vehicle: Vehicle {type A <: P} ): Vehicle = vehicle match { + case Car(_) => Car(100) + } + +} diff --git a/tests/patmat/t9657.scala.ignore b/tests/patmat/t9657.scala.ignore deleted file mode 100644 index d15a441e3237..000000000000 --- a/tests/patmat/t9657.scala.ignore +++ /dev/null @@ -1,28 +0,0 @@ -sealed trait PowerSource - -case object Petrol extends PowerSource - -case object Pedal extends PowerSource - -sealed abstract class Vehicle { - type A <: PowerSource -} - -case object Bicycle extends Vehicle { - type A = Pedal.type -} - -case class Bus(fuel: Int) extends Vehicle { - type A = Petrol.type -} - -case class Car(fuel: Int) extends Vehicle { - type A = Petrol.type -} - -object Test { - def refuel[P <: Petrol.type](vehicle: Vehicle {type A = P} ): Vehicle = vehicle match { - case Car(_) => Car(100) - case Bus(_) => Bus(100) - } -} \ No newline at end of file From c79181d08230b5387fb74e0c9fa3de8a6c56830d Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Mon, 4 Jul 2016 00:59:51 +0200 Subject: [PATCH 11/11] address counter-example of two unrelated traits --- src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/transform/PatternMatcher.scala | 9 +- .../tools/dotc/transform/patmat/Space.scala | 358 ++++++++++-------- tests/patmat/NonAbstractSealed.check | 5 + tests/patmat/NonAbstractSealed.scala | 10 + tests/patmat/TwoTrait.scala | 12 + tests/patmat/patmatexhaust.check | 2 +- tests/patmat/t9677.check | 4 + 8 files changed, 239 insertions(+), 163 deletions(-) create mode 100644 tests/patmat/NonAbstractSealed.check create mode 100644 tests/patmat/NonAbstractSealed.scala create mode 100644 tests/patmat/TwoTrait.scala create mode 100644 tests/patmat/t9677.check diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 83998303dc27..3c051b952c45 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -811,7 +811,7 @@ object Types { } /** Eliminate anonymous classes */ - final def elimAnonymousClass(implicit ctx: Context): Type = this match { + 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 diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index af742420c4cb..accdc762f66d 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1254,13 +1254,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. * @@ -1274,7 +1267,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans def translateMatch(match_ : Match): Tree = { val Match(sel, cases) = match_ - val selectorTp = sel.tpe.widen.elimAnonymousClass/*withoutAnnotations*/ + val selectorTp = sel.tpe.widen.deAnonymize/*withoutAnnotations*/ val selectorSym = freshSym(sel.pos, selectorTp, "selector") diff --git a/src/dotty/tools/dotc/transform/patmat/Space.scala b/src/dotty/tools/dotc/transform/patmat/Space.scala index 6d5323d08373..0829e6c34161 100644 --- a/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -8,7 +8,6 @@ import core.Flags._ import ast.Trees._ import ast.tpd import core.Decorators._ -import core.TypeApplications._ import core.Symbols._ import core.StdNames._ import core.NameOps._ @@ -43,13 +42,37 @@ import core.Constants._ /** space definition */ sealed trait Space + +/** Empty space */ case object Empty extends Space + +/** Space representing the set of all values of a type + * + * @param tp: the type this space represents + * @param decomposed: does the space result from decomposition? Used for pretty print + * + * Note: When transform patterns like `_: List[T]` into space, the type parameter is + * erased. E.g., `_: List[Int]` will be erased to `Typ(List, _)`. + * + * However, the scrutinee of pattern matching is not erased in order to make + * pattern matching for GADT more precise. + */ case class Typ(tp: Type, decomposed: Boolean) extends Space + +/** Space representing a constructor pattern */ case class Kon(tp: Type, params: List[Space]) extends Space + +/** Union of spaces */ +case class Or(spaces: List[Space]) extends Space + +/** Point in space */ sealed trait Point extends Space + +/** Point representing variables(stable identifier) in patterns */ case class Var(sym: Symbol, tp: Type) extends Point + +/** Point representing literal constants in patterns */ case class Const(value: Constant, tp: Type) extends Point -case class Or(spaces: List[Space]) extends Space /** abstract space logic */ trait SpaceLogic { @@ -59,7 +82,13 @@ trait SpaceLogic { /** Is `tp1` the same type as `tp2`? */ def isEqualType(tp1: Type, tp2: Type): Boolean - /** Is `tp` a case class? */ + /** Is `tp` a case class? + * + * Assumption: + * (1) One case class cannot be inherited directly or indirectly by another + * case class. + * (2) Inheritance of a case class cannot be well handled by the algorithm. + */ def isCaseClass(tp: Type): Boolean /** Is the type `tp` decomposable? i.e. all values of the type can be covered @@ -69,17 +98,17 @@ trait SpaceLogic { */ def canDecompose(tp: Type): Boolean - /** Return parameters types of the case class `tp` */ + /** Return term parameter types of the case class `tp` */ def signature(tp: Type): List[Type] /** Get components of decomposable types */ - def partitions(tp: Type): List[Space] + def decompose(tp: Type): List[Space] /** Simplify space using the laws, there's no nested union after simplify */ def simplify(space: Space): Space = space match { case Kon(tp, spaces) => val sp = Kon(tp, spaces.map(simplify _)) - if (sp.params.exists(_ == Empty)) Empty + if (sp.params.contains(Empty)) Empty else sp case Or(spaces) => val set = spaces.map(simplify _).flatMap { @@ -91,7 +120,7 @@ trait SpaceLogic { else if (set.size == 1) set.toList(0) else Or(set) case Typ(tp, _) => - if (canDecompose(tp) && partitions(tp).isEmpty) Empty + if (canDecompose(tp) && decompose(tp).isEmpty) Empty else space case _ => space } @@ -111,127 +140,157 @@ trait SpaceLogic { } /** Is `a` a subspace of `b`? Equivalent to `a - b == Empty`, but faster */ - def subspace(a: Space, b: Space): Boolean = (a, b) match { - case (Empty, _) => true - case (_, Empty) => false - case (Or(ss), _) => ss.forall(subspace(_, b)) - case (Typ(tp1, _), Typ(tp2, _)) => - isSubType(tp1, tp2) - case (Typ(tp1, _), Or(ss)) => - ss.exists(subspace(a, _)) || - (canDecompose(tp1) && subspace(Or(partitions(tp1)), b)) - case (Typ(tp1, _), Kon(tp2, ss)) => - isSubType(tp1, tp2) && subspace(Kon(tp2, signature(tp2).map(Typ(_, false))), b) - case (Kon(tp1, ss), Typ(tp2, _)) => - isSubType(tp1, tp2) || - simplify(a) == Empty || - (isSubType(tp2, tp1) && - canDecompose(tp1) && - subspace(Or(partitions(tp1)), b)) - case (Kon(_, _), Or(_)) => - simplify(minus(a, b)) == Empty - case (Kon(tp1, ss1), Kon(tp2, ss2)) => - isEqualType(tp1, tp2) && ss1.zip(ss2).forall((subspace _).tupled) - case (Const(v1, _), Const(v2, _)) => v1 == v2 - case (Const(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) - case (Const(_, _), Or(ss)) => ss.exists(subspace(a, _)) - case (Const(_, _), _) => false - case (_, Const(_, _)) => false - case (Var(x, _), Var(y, _)) => x == y - case (Var(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) - case (Var(_, _), Or(ss)) => ss.exists(subspace(a, _)) - case (Var(_, _), _) => false - case (_, Var(_, _)) => false + def isSubspace(a: Space, b: Space): Boolean = { + def tryDecompose1(tp: Type) = canDecompose(tp) && isSubspace(Or(decompose(tp)), b) + def tryDecompose2(tp: Type) = canDecompose(tp) && isSubspace(a, Or(decompose(tp))) + + (a, b) match { + case (Empty, _) => true + case (_, Empty) => false + case (Or(ss), _) => ss.forall(isSubspace(_, b)) + case (Typ(tp1, _), Typ(tp2, _)) => + isSubType(tp1, tp2) || tryDecompose1(tp1) || tryDecompose2(tp2) + case (Typ(tp1, _), Or(ss)) => + ss.exists(isSubspace(a, _)) || tryDecompose1(tp1) + case (Typ(tp1, _), Kon(tp2, ss)) => + isSubType(tp1, tp2) && isSubspace(Kon(tp2, signature(tp2).map(Typ(_, false))), b) || + tryDecompose1(tp1) + case (Kon(tp1, ss), Typ(tp2, _)) => + isSubType(tp1, tp2) || + simplify(a) == Empty || + (isSubType(tp2, tp1) && tryDecompose1(tp1)) || + tryDecompose2(tp2) + case (Kon(_, _), Or(_)) => + simplify(minus(a, b)) == Empty + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + isEqualType(tp1, tp2) && ss1.zip(ss2).forall((isSubspace _).tupled) + case (Const(v1, _), Const(v2, _)) => v1 == v2 + case (Const(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) || tryDecompose2(tp2) + case (Const(_, _), Or(ss)) => ss.exists(isSubspace(a, _)) + case (Const(_, _), _) => false + case (_, Const(_, _)) => false + case (Var(x, _), Var(y, _)) => x == y + case (Var(_, tp1), Typ(tp2, _)) => isSubType(tp1, tp2) || tryDecompose2(tp2) + case (Var(_, _), Or(ss)) => ss.exists(isSubspace(a, _)) + case (Var(_, _), _) => false + case (_, Var(_, _)) => false + } } /** Intersection of two spaces */ - def intersect(a: Space, b: Space): Space = (a, b) match { - case (Empty, _) | (_, Empty) => Empty - case (_, Or(ss)) => Or(ss.map(intersect(a, _))) - case (Or(ss), _) => Or(ss.map(intersect(_, b))) - case (Typ(tp1, _), Typ(tp2, _)) => - if (isSubType(tp1, tp2)) a - else if (isSubType(tp2, tp1)) b - else Empty - case (Typ(tp1, _), Kon(tp2, ss)) => - if (isSubType(tp2, tp1)) b - else if (isSubType(tp1, tp2)) a - else Empty - case (Kon(tp1, ss), Typ(tp2, _)) => - if (isSubType(tp1, tp2) || isSubType(tp2, tp1)) a - else Empty - case (Kon(tp1, ss1), Kon(tp2, ss2)) => - if (!isEqualType(tp1, tp2)) Empty - else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) Empty - else - Kon(tp1, ss1.zip(ss2).map((intersect _).tupled)) - case (Const(v1, _), Const(v2, _)) => - if (v1 == v2) a else Empty - case (Const(_, tp1), Typ(tp2, _)) => - if (isSubType(tp1, tp2)) a else Empty - case (Const(_, _), _) => Empty - case (Typ(tp1, _), Const(_, tp2)) => - if (isSubType(tp2, tp1)) b else Empty - case (_, Const(_, _)) => Empty - case (Var(x, _), Var(y, _)) => - if (x == y) a else Empty - case (Var(_, tp1), Typ(tp2, _)) => - if (isSubType(tp1, tp2)) a else Empty - case (Var(_, _), _) => Empty - case (Typ(tp1, _), Var(_, tp2)) => - if (isSubType(tp2, tp1)) b else Empty - case (_, Var(_, _)) => Empty + def intersect(a: Space, b: Space): Space = { + def tryDecompose1(tp: Type) = intersect(Or(decompose(tp)), b) + def tryDecompose2(tp: Type) = intersect(a, Or(decompose(tp))) + + (a, b) match { + case (Empty, _) | (_, Empty) => Empty + case (_, Or(ss)) => Or(ss.map(intersect(a, _)).filterConserve(_ ne Empty)) + case (Or(ss), _) => Or(ss.map(intersect(_, b)).filterConserve(_ ne Empty)) + case (Typ(tp1, _), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (isSubType(tp2, tp1)) b + else if (canDecompose(tp1)) tryDecompose1(tp1) + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Typ(tp1, _), Kon(tp2, ss)) => + if (isSubType(tp2, tp1)) b + else if (isSubType(tp1, tp2)) a // problematic corner case: inheriting a case class + else if (canDecompose(tp1)) tryDecompose1(tp1) + else Empty + case (Kon(tp1, ss), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (isSubType(tp2, tp1)) a // problematic corner case: inheriting a case class + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + if (!isEqualType(tp1, tp2)) Empty + else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) Empty + else Kon(tp1, ss1.zip(ss2).map((intersect _).tupled)) + case (Const(v1, _), Const(v2, _)) => + if (v1 == v2) a else Empty + case (Const(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Const(_, _), _) => Empty + case (Typ(tp1, _), Const(_, tp2)) => + if (isSubType(tp2, tp1)) b + else if (canDecompose(tp1)) tryDecompose1(tp1) + else Empty + case (_, Const(_, _)) => Empty + case (Var(x, _), Var(y, _)) => + if (x == y) a else Empty + case (Var(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) a + else if (canDecompose(tp2)) tryDecompose2(tp2) + else Empty + case (Var(_, _), _) => Empty + case (Typ(tp1, _), Var(_, tp2)) => + if (isSubType(tp2, tp1)) b + else if (canDecompose(tp1)) tryDecompose1(tp1) + else Empty + case (_, Var(_, _)) => Empty + } } /** The space of a not covered by b */ - def minus(a: Space, b: Space): Space = (a, b) match { - case (Empty, _) => Empty - case (_, Empty) => a - case (Typ(tp1, _), Typ(tp2, _)) => - if (isSubType(tp1, tp2)) Empty - else if (isSubType(tp2, tp1) && canDecompose(tp1)) - minus(Or(partitions(tp1)), b) - else a - case (Typ(tp1, _), Kon(tp2, ss)) => - if (isSubType(tp1, tp2)) minus(Kon(tp2, signature(tp2).map(Typ(_, false))), b) - else if (isSubType(tp2, tp1) && canDecompose(tp1)) - minus(Or(partitions(tp1)), b) - else a - case (_, Or(ss)) => - ss.foldLeft(a)(minus) - case (Or(ss), _) => - Or(ss.map(minus(_, b))) - case (Kon(tp1, ss), Typ(tp2, _)) => - if (isSubType(tp1, tp2)) Empty - else if (simplify(a) == Empty) Empty - else if (isSubType(tp2, tp1) && canDecompose(tp1)) - minus(Or(partitions(tp1)), b) - else a - case (Kon(tp1, ss1), Kon(tp2, ss2)) => - if (!isEqualType(tp1, tp2)) a - else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) a - else if (ss1.zip(ss2).forall((subspace _).tupled)) Empty - else - Or( - ss1.zip(ss2).map((minus _).tupled).zip(0 to ss2.length - 1).map { - case (ri, i) => Kon(tp1, ss1.updated(i, ri)) - }) - case (Const(v1, _), Const(v2, _)) => - if (v1 == v2) Empty else a - case (Const(_, tp1), Typ(tp2, _)) => - if (isSubType(tp1, tp2)) Empty else a - case (Const(_, _), _) => a - case (Typ(tp1, _), Const(_, tp2)) => // Boolean & Java enum - if (isSubType(tp2, tp1) && canDecompose(tp1)) - minus(Or(partitions(tp1)), b) - else a - case (_, Const(_, _)) => a - case (Var(x, _), Var(y, _)) => - if (x == y) Empty else a - case (Var(_, tp1), Typ(tp2, _)) => - if (isSubType(tp1, tp2)) Empty else a - case (Var(_, _), _) => a - case (_, Var(_, _)) => a + def minus(a: Space, b: Space): Space = { + def tryDecompose1(tp: Type) = minus(Or(decompose(tp)), b) + def tryDecompose2(tp: Type) = minus(a, Or(decompose(tp))) + + (a, b) match { + case (Empty, _) => Empty + case (_, Empty) => a + case (Typ(tp1, _), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (canDecompose(tp1)) tryDecompose1(tp1) + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Typ(tp1, _), Kon(tp2, ss)) => + // corner case: inheriting a case class + // rationale: every instance of `tp1` is covered by `tp2(_)` + if (isSubType(tp1, tp2)) minus(Kon(tp2, signature(tp2).map(Typ(_, false))), b) + else if (canDecompose(tp1)) tryDecompose1(tp1) + else a + case (_, Or(ss)) => + ss.foldLeft(a)(minus) + case (Or(ss), _) => + Or(ss.map(minus(_, b))) + case (Kon(tp1, ss), Typ(tp2, _)) => + // uncovered corner case: tp2 :< tp1 + if (isSubType(tp1, tp2)) Empty + else if (simplify(a) == Empty) Empty + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Kon(tp1, ss1), Kon(tp2, ss2)) => + if (!isEqualType(tp1, tp2)) a + else if (ss1.zip(ss2).exists(p => simplify(intersect(p._1, p._2)) == Empty)) a + else if (ss1.zip(ss2).forall((isSubspace _).tupled)) Empty + else + // `(_, _, _) - (Some, None, _)` becomes `(None, _, _) | (_, Some, _) | (_, _, Empty)` + Or(ss1.zip(ss2).map((minus _).tupled).zip(0 to ss2.length - 1).map { + case (ri, i) => Kon(tp1, ss1.updated(i, ri)) + }) + case (Const(v1, _), Const(v2, _)) => + if (v1 == v2) Empty else a + case (Const(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Const(_, _), _) => a + case (Typ(tp1, _), Const(_, tp2)) => // Boolean & Java enum + if (canDecompose(tp1)) tryDecompose1(tp1) + else a + case (_, Const(_, _)) => a + case (Var(x, _), Var(y, _)) => + if (x == y) Empty else a + case (Var(_, tp1), Typ(tp2, _)) => + if (isSubType(tp1, tp2)) Empty + else if (canDecompose(tp2)) tryDecompose2(tp2) + else a + case (Var(_, _), _) => a + case (_, Var(_, _)) => a + } } } @@ -271,15 +330,22 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { Empty } - /* Erase a type binding according to erasure rule */ + /* Erase a type binding according to erasure semantics in pattern matching */ def erase(tp: Type): Type = { def doErase(tp: Type): Type = tp match { case tp: RefinedType => erase(tp.parent) case _ => tp } - val origin = doErase(tp) - if (origin =:= defn.ArrayType) tp else origin + tp match { + case OrType(tp1, tp2) => + OrType(erase(tp1), erase(tp2)) + case AndType(tp1, tp2) => + AndType(erase(tp1), erase(tp2)) + case _ => + val origin = doErase(tp) + if (origin =:= defn.ArrayType) tp else origin + } } /** Is `tp1` a subtype of `tp2`? */ @@ -295,32 +361,18 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def signature(tp: Type): List[Type] = { val ktor = tp.classSymbol.primaryConstructor.info - val meth = - if (ktor.isInstanceOf[MethodType]) ktor - else - tp match { - case AppliedType(_, params) => - val refined = params.map { - // TypeBounds would generate an exception - case tp: TypeBounds => tp.underlying - case tp => tp - } - ktor.appliedTo(refined) - case _ => - ktor - } + val meth = ktor match { + case ktor: PolyType => + ktor.instantiate(tp.classSymbol.typeParams.map(_.typeRef)).asSeenFrom(tp, tp.classSymbol) + case _ => ktor + } // refine path-dependent type in params. refer to t9672 - meth.firstParamTypes.map(_.stripTypeVar).map { ptp => - (tp, ptp) match { - case (TypeRef(ref1: TypeProxy, _), TypeRef(ref2: TypeProxy, name)) => - if (ref1.underlying <:< ref2.underlying) TypeRef(ref1, name) else ptp - case _ => ptp - } - } + meth.firstParamTypes.map(_.asSeenFrom(tp, tp.classSymbol)) } - def partitions(tp: Type): List[Space] = { + /** Decompose a type into subspaces -- assume the type can be decomposed */ + def decompose(tp: Type): List[Space] = { val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot => // refer to definition of Annotation.makeChild annot.tree match { @@ -342,7 +394,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { if (sym.is(ModuleClass)) sym.asClass.classInfo.selfType else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef]) - refine(tp, sym.asClass.classInfo.symbolicTypeRef) + refine(tp, sym.typeRef) else sym.typeRef } filter { tpe => @@ -477,7 +529,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { } val Match(sel, cases) = tree - isCheckable(sel.tpe.widen.elimAnonymousClass) + isCheckable(sel.tpe.widen.deAnonymize) } @@ -487,7 +539,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { * * A <: X :> Y M { type T = A } ~~> M { type T <: X :> Y } * - * A <: X :> Y B <: U :> V M { type T <: A :> B } ~~> M { type T <: X :> B } + * A <: X :> Y B <: U :> V M { type T <: A :> B } ~~> M { type T <: X :> V } * * A = X B = Y M { type T <: A :> B } ~~> M { type T <: X :> Y } */ @@ -535,7 +587,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def checkExhaustivity(_match: Match): Unit = { val Match(sel, cases) = _match - val selTyp = sel.tpe.widen.elimAnonymousClass.dealias + val selTyp = sel.tpe.widen.deAnonymize.dealias val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or(List(a, b))) @@ -553,7 +605,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def checkRedundancy(_match: Match): Unit = { val Match(sel, cases) = _match // ignore selector type for now - // val selTyp = sel.tpe.widen.elimAnonymousClass.dealias + // val selTyp = sel.tpe.widen.deAnonymize.dealias // starts from the second, the first can't be redundant (1 until cases.length).foreach { i => @@ -566,7 +618,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { val curr = project(cases(i).pat) - if (subspace(curr, prevs)) { + if (isSubspace(curr, prevs)) { ctx.warning("unreachable code", cases(i).body.pos) } } diff --git a/tests/patmat/NonAbstractSealed.check b/tests/patmat/NonAbstractSealed.check new file mode 100644 index 000000000000..9224ee370ce0 --- /dev/null +++ b/tests/patmat/NonAbstractSealed.check @@ -0,0 +1,5 @@ +./tests/patmat/NonAbstractSealed.scala:6: warning: match may not be exhaustive. +It would fail on the following input: _: A + (null: A) match { + ^ +one warning found \ No newline at end of file diff --git a/tests/patmat/NonAbstractSealed.scala b/tests/patmat/NonAbstractSealed.scala new file mode 100644 index 000000000000..ff2e90aeeb05 --- /dev/null +++ b/tests/patmat/NonAbstractSealed.scala @@ -0,0 +1,10 @@ +sealed class A +class B extends A +class C extends A + +object Test { + (null: A) match { + case t: B => + case t: C => + } +} diff --git a/tests/patmat/TwoTrait.scala b/tests/patmat/TwoTrait.scala new file mode 100644 index 000000000000..b8e3402c5e8c --- /dev/null +++ b/tests/patmat/TwoTrait.scala @@ -0,0 +1,12 @@ +object Test { + sealed trait A + sealed trait B + + abstract sealed class Parent + class Foo extends Parent with A with B + class Bar extends Parent with B with A + + (null: A) match { + case _: B => + } +} diff --git a/tests/patmat/patmatexhaust.check b/tests/patmat/patmatexhaust.check index 5121b73638a8..ef2b578d6e3b 100644 --- a/tests/patmat/patmatexhaust.check +++ b/tests/patmat/patmatexhaust.check @@ -23,7 +23,7 @@ It would fail on the following input: _: C1 def ma10(x: C) = x match { // not exhaustive: C1 is not sealed. ^ ./tests/patmat/patmatexhaust.scala:114: warning: match may not be exhaustive. -It would fail on the following input: _: C1 +It would fail on the following input: D2(), D1 def ma10(x: C) = x match { // not exhaustive: C1 has subclasses. ^ ./tests/patmat/patmatexhaust.scala:126: warning: match may not be exhaustive. diff --git a/tests/patmat/t9677.check b/tests/patmat/t9677.check new file mode 100644 index 000000000000..f1e1817cbbaa --- /dev/null +++ b/tests/patmat/t9677.check @@ -0,0 +1,4 @@ +./tests/patmat/t9677.scala:20: warning: unreachable code + case path: A => println("Not root") + ^ +one warning found \ No newline at end of file