diff options
author | Burak Emir <emir@epfl.ch> | 2007-02-02 10:29:43 +0000 |
---|---|---|
committer | Burak Emir <emir@epfl.ch> | 2007-02-02 10:29:43 +0000 |
commit | 6a440b960c00c01f3653385417a246e359d82e01 (patch) | |
tree | 195c8b3d506d37c5ba049f3495cd43bdf3a3e575 | |
parent | df3c09479ed116752324839ccf2ca4a1e9890d9a (diff) | |
download | scala-6a440b960c00c01f3653385417a246e359d82e01.tar.gz scala-6a440b960c00c01f3653385417a246e359d82e01.tar.bz2 scala-6a440b960c00c01f3653385417a246e359d82e01.zip |
matching: + exhaustivity check, warnings
Iterator: gets mkString method
Iterable: only whitespace
Definitions: value classes no longer SEALED
test cases for exhaustivity + unapply/array
-rw-r--r-- | src/compiler/scala/tools/nsc/matching/PatternMatchers.scala | 76 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/Definitions.scala | 2 | ||||
-rw-r--r-- | src/library/scala/Iterator.scala | 39 | ||||
-rw-r--r-- | test/files/neg/patmatexhaust.check | 20 | ||||
-rw-r--r-- | test/files/neg/patmatexhaust.scala | 59 | ||||
-rw-r--r-- | test/files/run/unapplyArray.scala | 31 |
6 files changed, 218 insertions, 9 deletions
diff --git a/src/compiler/scala/tools/nsc/matching/PatternMatchers.scala b/src/compiler/scala/tools/nsc/matching/PatternMatchers.scala index d3217c8672..466f4e96c9 100644 --- a/src/compiler/scala/tools/nsc/matching/PatternMatchers.scala +++ b/src/compiler/scala/tools/nsc/matching/PatternMatchers.scala @@ -1002,44 +1002,104 @@ print() LabelDef(exit, List(result), Ident(result))) } + type SymSet = collection.immutable.Set[Symbol] + /*protected*/ def toTree(node1: PatternNode): Tree = { - def optimize1(selType:Type, alternatives1: PatternNode ): Boolean = { + + def checkEx(tpesym:Symbol) = tpesym.hasFlag(Flags.SEALED) + def checkExCoverage(tpesym:Symbol): SymSet = if(!checkEx(tpesym)) + emptySymbolSet else { + //Console.println("/"+tpesym) + //Console.println(tpesym.children) + tpesym.children.flatMap { x => if(x.hasFlag(Flags.CASE)) (emptySymbolSet+x) else checkExCoverage(x) } + } + def andIsUnguardedBody(p1:PatternNode) = p1.and match { + case p: Body => p.guard.length == 1 && p.guard(0) == EmptyTree + case _ => false + } + def andIsGuarded(p1:PatternNode) = p1.and match { + case p: Body => p.guard.length >= 1 && p.guard(0) == EmptyTree + case _ => false + } + + + // returns true if this tree is optimizable + // throws a warning if is not exhaustive + def optimize1(selType:Type, alternatives1: PatternNode ): {Boolean, collection.immutable.Set[Symbol], collection.immutable.Set[Symbol]} = { +// if selType hasflag SEALED, enumerate selType.symbol.children +// to be very precise, take into account the case that a non case child is sealed. var alts = alternatives1; if (!optimize || !isSubType(selType, defs.ScalaObjectClass.tpe)) - return false; + return {false, null, emptySymbolSet}; + // contains cases that have not been covered + var coveredCases: SymSet = emptySymbolSet // only case _ + var remainingCases = if(alts ne null) checkExCoverage(selType.symbol) else emptySymbolSet // only case _ var cases = 0; while (alts ne null) { alts match { case ConstrPat(_) => - if (alts.getTpe().symbol.hasFlag(Flags.CASE)) + coveredCases = coveredCases + alts.getTpe.symbol + remainingCases = remainingCases - alts.getTpe.symbol + if (alts.getTpe.symbol.hasFlag(Flags.CASE)) cases = cases +1; else - return false; + return {false, coveredCases, remainingCases}; case DefaultPat() => - ; + if(andIsUnguardedBody(alts) || alts.and.isInstanceOf[Header]) { + coveredCases = emptySymbolSet + remainingCases = emptySymbolSet + } case _ => - return false; + return {false, coveredCases, remainingCases}; } alts = alts.or; } - return cases > 2; + return {cases > 2, coveredCases, remainingCases}; } // def optimize var node = node1; var res: Tree = Literal(Constant(false)); //.setInfo(defs.BooleanClass); + var lastSelector: Tree = null + var carryCovered: SymSet = emptySymbolSet while (node ne null) node match { case _h:Header => val selector = _h.selector; + if(selector != lastSelector) { + carryCovered = emptySymbolSet; + } + //Console.println("sel:"+selector+" last"+lastSelector+" - "+(selector == lastSelector)) val next = _h.next; //res = And(mkNegate(res), toTree(node.or, selector)); - if (optimize1(node.getTpe(), node.or)) + val {doOptimize, coveredCases, remainingCases} = optimize1(node.getTpe, node.or) + if(!remainingCases.isEmpty) { + carryCovered = carryCovered ++ coveredCases // ?? + if(next != null && andIsUnguardedBody(next.or)) { + // ignore, default case + } else if(next ne null) { + // ignore, more headers to come + // Console.println(next.print("", new StringBuilder()).toString()) + } else { + val realRemainingCases = remainingCases -- carryCovered + //Console.println("remain "+remainingCases+" carry covered "+ carryCovered + "real "+realRemainingCases) + if(!realRemainingCases.isEmpty) { + val word = if(realRemainingCases.size > 1) "cases " else "case " + cunit.warning(node.pos, "does not cover "+word+realRemainingCases.elements.mkString("{",",","}")) + //Console.println(_h.print("", new StringBuilder()).toString()) + } + } + } + if (doOptimize) res = Or(res, toOptTree(node.or, selector)); else res = Or(res, toTree(node.or, selector)); + + //Console.println("!carry covered "+ carryCovered) + + lastSelector = selector node = next; case _b:Body => diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala index d1478f629c..9d45255ad0 100644 --- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala +++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala @@ -429,7 +429,7 @@ trait Definitions requires SymbolTable { "scala.runtime.Boxed" + name val clazz = newClass(ScalaPackageClass, name, List(AnyValClass.typeConstructor)) - .setFlag(ABSTRACT | SEALED) + .setFlag(ABSTRACT /* SEALED */) // bq: SEALED is interesting for case class descendants, only boxedClass(clazz) = getClass(boxedName) boxedArrayClass(clazz) = getClass("scala.runtime.Boxed" + name + "Array") refClass(clazz) = getClass("scala.runtime." + name + "Ref") diff --git a/src/library/scala/Iterator.scala b/src/library/scala/Iterator.scala index 4fccc09201..1ebd4c5c0d 100644 --- a/src/library/scala/Iterator.scala +++ b/src/library/scala/Iterator.scala @@ -14,6 +14,7 @@ package scala import Predef._ import collection.mutable.{Buffer, ArrayBuffer} +import compat.StringBuilder /** The <code>Iterator</code> object provides various functions for * creating specialized iterators. @@ -605,4 +606,42 @@ trait Iterator[+A] { } res.toList } + + /** Returns a string representation of the elements in this iterator. The resulting string + * begins with the string <code>start</code> and is finished by the string + * <code>end</code>. Inside, the string representations of elements (w.r.t. + * the method <code>toString()</code>) are separated by the string + * <code>sep</code>. + * <p/> + * Ex: <br/> + * <code>List(1, 2, 3).mkString("(", "; ", ")") = "(1; 2; 3)"</code> + * + * @param start starting string. + * @param sep separator string. + * @param end ending string. + * @return a string representation of this iterable object. + */ + def mkString(start: String, sep: String, end: String): String = { + val buf = new StringBuilder() + addString(buf, start, sep, end).toString + } + + /** Returns a string representation of this iterable object. The string + * representations of elements (w.r.t. the method <code>toString()</code>) + * are separated by the string <code>sep</code>. + * + * @param sep separator string. + * @return a string representation of this iterable object. */ + def mkString(sep: String): String = this.mkString("", sep, "") + + /** Write all elements of this string into given string builder */ + def addString(buf: StringBuilder, start: String, sep: String, end: String): StringBuilder = { + buf.append(start) + val elems = this + if (elems.hasNext) buf.append(elems.next) + while (elems.hasNext) { + buf.append(sep); buf.append(elems.next) + } + buf.append(end) + } } diff --git a/test/files/neg/patmatexhaust.check b/test/files/neg/patmatexhaust.check new file mode 100644 index 0000000000..40054bad9f --- /dev/null +++ b/test/files/neg/patmatexhaust.check @@ -0,0 +1,20 @@ +patmatexhaust.scala:8: warning: does not cover case {object Baz} + def ma1(x:Foo) = x match { + ^ +patmatexhaust.scala:12: warning: does not cover case {class Bar} + def ma2(x:Foo) = x match { + ^ +patmatexhaust.scala:24: warning: does not cover case {class Kult} + case {Kult(_), Qult()} => // Kult missing + ^ +patmatexhaust.scala:26: warning: does not cover case {class Qult} + case {Qult(), Kult(_)} => // Qult missing + ^ +patmatexhaust.scala:45: warning: does not cover case {object Gu} + def ma4(x:Deep) = x match { // missing cases: Gu + ^ +patmatexhaust.scala:57: error: unreachable code + case 1 => + ^ +5 warnings found +one error found diff --git a/test/files/neg/patmatexhaust.scala b/test/files/neg/patmatexhaust.scala new file mode 100644 index 0000000000..0416749b88 --- /dev/null +++ b/test/files/neg/patmatexhaust.scala @@ -0,0 +1,59 @@ +class TestSealedExhaustive { // compile only + + sealed 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 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()} => + } + + + sealed 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 + case Ga => + } + + def zma5(x:Deep) = x match { // exhaustive + case Gu => + case _ if 1 == 0 => + case Ga => + } + + def redundant = 1 match { // include this otherwise script won't test this in files/neg + case 1 => + case 1 => + } +} diff --git a/test/files/run/unapplyArray.scala b/test/files/run/unapplyArray.scala new file mode 100644 index 0000000000..bf6582dadf --- /dev/null +++ b/test/files/run/unapplyArray.scala @@ -0,0 +1,31 @@ +object Test { + def main(args:Array[String]): Unit = { + val z = Array(1,2,3,4) + val zs: Seq[int] = z + val za: Any = z + +/* + Console.println("z is arr[int]"+z.isInstanceOf[Array[int]]) + Console.println("zs is arr[int]"+zs.isInstanceOf[Array[int]]) + Console.println("za is arr[int]"+ za.isInstanceOf[Array[int]]) + + Console.println("z is seq[int]"+z.isInstanceOf[Seq[int]]) + Console.println("zs is seq[int]"+zs.isInstanceOf[Seq[int]]) + Console.println("za is seq[int]"+ za.isInstanceOf[Seq[int]]) + + Console.println("z is anyref"+z.isInstanceOf[AnyRef]) + + Console.println("z useq "+ Seq.unapplySeq(z)) + Console.println("zs useq "+ Seq.unapplySeq(zs)) + Console.println("za useq "+ Seq.unapplySeq(za)) + + Console.println("z aseq "+ Seq.unapplySeq(z)) + Console.println("zs aseq "+ Seq.unapplySeq(zs)) + Console.println("za aseq "+ Seq.unapplySeq(za)) +*/ + val zl = zs match { + case Seq(xs@_*) => xs.length + } + assert(zl == 4) + } +} |