diff options
18 files changed, 638 insertions, 225 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 39e259fdfd..86bb99e7fa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -1132,7 +1132,7 @@ trait Namers extends MethodSynthesis { } } - addDefaultGetters(meth, vparamss, tparams, overriddenSymbol(methResTp)) + addDefaultGetters(meth, ddef, vparamss, tparams, overriddenSymbol(methResTp)) // fast track macros, i.e. macros defined inside the compiler, are hardcoded // hence we make use of that and let them have whatever right-hand side they need @@ -1174,7 +1174,12 @@ trait Namers extends MethodSynthesis { * typechecked, the corresponding param would not yet have the "defaultparam" * flag. */ - private def addDefaultGetters(meth: Symbol, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) { + private def addDefaultGetters(meth: Symbol, ddef: DefDef, vparamss: List[List[ValDef]], tparams: List[TypeDef], overriddenSymbol: => Symbol) { + val DefDef(_, _, rtparams0, rvparamss0, _, _) = resetLocalAttrs(ddef.duplicate) + // having defs here is important to make sure that there's no sneaky tree sharing + // in methods with multiple default parameters + def rtparams = rtparams0.map(_.duplicate) + def rvparamss = rvparamss0.map(_.map(_.duplicate)) val methOwner = meth.owner val isConstr = meth.isConstructor val overridden = if (isConstr || !methOwner.isClass) NoSymbol else overriddenSymbol @@ -1206,23 +1211,36 @@ trait Namers extends MethodSynthesis { // vparamss.foldLeft(Nil: List[List[ValDef]]) { (previous, vparams) => assert(!overrides || vparams.length == baseParamss.head.length, ""+ meth.fullName + ", "+ overridden.fullName) + val rvparams = rvparamss(previous.length) var baseParams = if (overrides) baseParamss.head else Nil - for (vparam <- vparams) { + map2(vparams, rvparams)((vparam, rvparam) => { val sym = vparam.symbol // true if the corresponding parameter of the base class has a default argument val baseHasDefault = overrides && baseParams.head.hasDefault if (sym.hasDefault) { - // generate a default getter for that argument + // Create a "default getter", i.e. a DefDef that will calculate vparam.rhs + // for those who are going to call meth without providing an argument corresponding to vparam. + // After the getter is created, a corresponding synthetic symbol is created and entered into the parent namer. + // + // In the ideal world, this DefDef would be a simple one-liner that just returns vparam.rhs, + // but in scalac things are complicated in two different ways. + // + // 1) Because the underlying language is quite sophisticated, we must allow for those sophistications in our getter. + // Namely: a) our getter has to copy type parameters from the associated method (or the associated class + // if meth is a constructor), because vparam.rhs might refer to one of them, b) our getter has to copy + // preceding value parameter lists from the associated method, because again vparam.rhs might refer to one of them. + // + // 2) Because we have already assigned symbols to type and value parameters that we have to copy, we must jump through + // hoops in order to destroy them and allow subsequent naming create new symbols for our getter. Previously this + // was done in an overly brutal way akin to resetAllAttrs, but now we utilize a resetLocalAttrs-based approach. + // Still far from ideal, but at least enables things like run/macro-default-params that were previously impossible. + val oflag = if (baseHasDefault) OVERRIDE else 0 val name = nme.defaultGetterName(meth.name, posCounter) - // Create trees for the defaultGetter. Uses tools from Unapplies.scala - var deftParams = tparams map copyUntyped[TypeDef] - val defvParamss = mmap(previous) { p => - // in the default getter, remove the default parameter - val p1 = atPos(p.pos.focus) { ValDef(p.mods &~ DEFAULTPARAM, p.name, p.tpt.duplicate, EmptyTree) } - UnTyper.traverse(p1) - p1 + var defTparams = rtparams + val defVparamss = mmap(rvparamss.take(previous.length)){ rvp => + copyValDef(rvp)(mods = rvp.mods &~ DEFAULTPARAM, rhs = EmptyTree) } val parentNamer = if (isConstr) { @@ -1244,7 +1262,8 @@ trait Namers extends MethodSynthesis { return // fix #3649 (prevent crash in erroneous source code) } } - deftParams = cdef.tparams map copyUntypedInvariant + val ClassDef(_, _, rtparams, _) = resetLocalAttrs(cdef.duplicate) + defTparams = rtparams.map(rt => copyTypeDef(rt)(mods = rt.mods &~ (COVARIANT | CONTRAVARIANT))) nmr } else ownerNamer getOrElse { @@ -1255,23 +1274,30 @@ trait Namers extends MethodSynthesis { nmr } - // If the parameter type mentions any type parameter of the method, let the compiler infer the - // return type of the default getter => allow "def foo[T](x: T = 1)" to compile. - // This is better than always using Wildcard for inferring the result type, for example in - // def f(i: Int, m: Int => Int = identity _) = m(i) - // if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable. - val names = deftParams map { case TypeDef(_, name, _, _) => name } - val subst = new TypeTreeSubstituter(names contains _) - - val defTpt = subst(copyUntyped(vparam.tpt match { - // default getter for by-name params - case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg - case t => t - })) - val defRhs = copyUntyped(vparam.rhs) + val defTpt = + // don't mess with tpt's of case copy default getters, because assigning something other than TypeTree() + // will break the carefully orchestrated naming/typing logic that involves enterCopyMethod and caseClassCopyMeth + if (meth.isCaseCopy) TypeTree() + else { + // If the parameter type mentions any type parameter of the method, let the compiler infer the + // return type of the default getter => allow "def foo[T](x: T = 1)" to compile. + // This is better than always using Wildcard for inferring the result type, for example in + // def f(i: Int, m: Int => Int = identity _) = m(i) + // if we use Wildcard as expected, we get "Nothing => Nothing", and the default is not usable. + // TODO: this is a very brittle approach; I sincerely hope that Denys's research into hygiene + // will open the doors to a much better way of doing this kind of stuff + val tparamNames = defTparams map { case TypeDef(_, name, _, _) => name } + val eraseAllMentionsOfTparams = new TypeTreeSubstituter(tparamNames contains _) + eraseAllMentionsOfTparams(rvparam.tpt match { + // default getter for by-name params + case AppliedTypeTree(_, List(arg)) if sym.hasFlag(BYNAMEPARAM) => arg + case t => t + }) + } + val defRhs = rvparam.rhs val defaultTree = atPos(vparam.pos.focus) { - DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags)) | oflag, name, deftParams, defvParamss, defTpt, defRhs) + DefDef(Modifiers(paramFlagsToDefaultGetter(meth.flags)) | oflag, name, defTparams, defVparamss, defTpt, defRhs) } if (!isConstr) methOwner.resetFlag(INTERFACE) // there's a concrete member now @@ -1286,7 +1312,7 @@ trait Namers extends MethodSynthesis { } posCounter += 1 if (overrides) baseParams = baseParams.tail - } + }) if (overrides) baseParamss = baseParamss.tail previous :+ vparams } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 61e7367547..dbe85f4f5a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -56,16 +56,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper resetDocComments() } - object UnTyper extends Traverser { - override def traverse(tree: Tree) = { - if (tree.canHaveAttrs) { - tree.clearType() - if (tree.hasSymbolField) tree.symbol = NoSymbol - } - super.traverse(tree) - } - } - sealed abstract class SilentResult[+T] { @inline final def fold[U](none: => U)(f: T => U): U = this match { case SilentResultValue(value) => f(value) diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index ed96f66ab8..ffac29b4b8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -43,12 +43,6 @@ trait Unapplies extends ast.TreeDSL { def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption } - def copyUntyped[T <: Tree](tree: T): T = - returning[T](tree.duplicate)(UnTyper traverse _) - - def copyUntypedInvariant(td: TypeDef): TypeDef = - copyTypeDef(copyUntyped(td))(mods = td.mods &~ (COVARIANT | CONTRAVARIANT)) - private def toIdent(x: DefTree) = Ident(x.name) setPos x.pos.focus private def classType(cdef: ClassDef, tparams: List[TypeDef]): Tree = { @@ -58,8 +52,15 @@ trait Unapplies extends ast.TreeDSL { } private def constrParamss(cdef: ClassDef): List[List[ValDef]] = { - val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor cdef.impl.body - mmap(vparamss)(copyUntyped[ValDef]) + val ClassDef(_, _, _, Template(_, _, body)) = resetLocalAttrs(cdef.duplicate) + val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor body + vparamss + } + + private def constrTparamsInvariant(cdef: ClassDef): List[TypeDef] = { + val ClassDef(_, _, tparams, _) = resetLocalAttrs(cdef.duplicate) + val tparamsInvariant = tparams.map(tparam => copyTypeDef(tparam)(mods = tparam.mods &~ (COVARIANT | CONTRAVARIANT))) + tparamsInvariant } /** The return value of an unapply method of a case class C[Ts] @@ -125,7 +126,7 @@ trait Unapplies extends ast.TreeDSL { /** The apply method corresponding to a case class */ def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = { - val tparams = cdef.tparams map copyUntypedInvariant + val tparams = constrTparamsInvariant(cdef) val cparamss = constrParamss(cdef) def classtpe = classType(cdef, tparams) atPos(cdef.pos.focus)( @@ -141,7 +142,7 @@ trait Unapplies extends ast.TreeDSL { /** The unapply method corresponding to a case class */ def caseModuleUnapplyMeth(cdef: ClassDef): DefDef = { - val tparams = cdef.tparams map copyUntypedInvariant + val tparams = constrTparamsInvariant(cdef) val method = constrParamss(cdef) match { case xs :: _ if xs.nonEmpty && isRepeatedParamType(xs.last.tpt) => nme.unapplySeq case _ => nme.unapply @@ -196,7 +197,7 @@ trait Unapplies extends ast.TreeDSL { treeCopy.ValDef(vd, Modifiers(flags), vd.name, tpt, rhs) } - val tparams = cdef.tparams map copyUntypedInvariant + val tparams = constrTparamsInvariant(cdef) val paramss = classParamss match { case Nil => Nil case ps :: pss => diff --git a/src/library/scala/collection/convert/DecorateAsJava.scala b/src/library/scala/collection/convert/DecorateAsJava.scala index 498bdc5943..6658b6feea 100644 --- a/src/library/scala/collection/convert/DecorateAsJava.scala +++ b/src/library/scala/collection/convert/DecorateAsJava.scala @@ -287,7 +287,7 @@ trait DecorateAsJava { * will be visible via the Scala interface and vice versa. * * If the Scala `concurrent.Map` was previously obtained from an implicit or - * explicit call of `asConcurrentMap(java.util.concurrect.ConcurrentMap)` + * explicit call of `asConcurrentMap(java.util.concurrent.ConcurrentMap)` * then the original Java `ConcurrentMap` will be returned. * * @param m The Scala `concurrent.Map` to be converted. diff --git a/src/library/scala/collection/convert/WrapAsJava.scala b/src/library/scala/collection/convert/WrapAsJava.scala index b6ebf2ff06..9916fe9843 100644 --- a/src/library/scala/collection/convert/WrapAsJava.scala +++ b/src/library/scala/collection/convert/WrapAsJava.scala @@ -244,7 +244,7 @@ trait WrapAsJava { * will be visible via the Scala interface and vice versa. * * If the Scala `concurrent.Map` was previously obtained from an implicit or - * explicit call of `mapAsScalaConcurrentMap(java.util.concurrect.ConcurrentMap)` + * explicit call of `mapAsScalaConcurrentMap(java.util.concurrent.ConcurrentMap)` * then the original Java ConcurrentMap will be returned. * * @param m The Scala `concurrent.Map` to be converted. diff --git a/src/library/scala/math/Numeric.scala b/src/library/scala/math/Numeric.scala index e6644c0dfc..eafbf96993 100644 --- a/src/library/scala/math/Numeric.scala +++ b/src/library/scala/math/Numeric.scala @@ -127,6 +127,8 @@ object Numeric { def toLong(x: Float): Long = x.toLong def toFloat(x: Float): Float = x def toDouble(x: Float): Double = x.toDouble + // logic in Numeric base trait mishandles abs(-0.0f) + override def abs(x: Float): Float = math.abs(x) } trait FloatIsFractional extends FloatIsConflicted with Fractional[Float] { def div(x: Float, y: Float): Float = x / y @@ -149,6 +151,8 @@ object Numeric { def toLong(x: Double): Long = x.toLong def toFloat(x: Double): Float = x.toFloat def toDouble(x: Double): Double = x + // logic in Numeric base trait mishandles abs(-0.0) + override def abs(x: Double): Double = math.abs(x) } trait DoubleIsFractional extends DoubleIsConflicted with Fractional[Double] { def div(x: Double, y: Double): Double = x / y diff --git a/src/library/scala/ref/WeakReference.scala b/src/library/scala/ref/WeakReference.scala index c8fb262a08..6ee40aed5c 100644 --- a/src/library/scala/ref/WeakReference.scala +++ b/src/library/scala/ref/WeakReference.scala @@ -10,7 +10,7 @@ package scala.ref /** - * A wrapper class for java.lag.ref.WeakReference + * A wrapper class for java.lang.ref.WeakReference * The new functionality is (1) results are Option values, instead of using null. * (2) There is an extractor that maps the weak reference itself into an option. * @author Sean McDirmid diff --git a/src/library/scala/sys/process/ProcessBuilder.scala b/src/library/scala/sys/process/ProcessBuilder.scala index feced71dae..88c0cf8e58 100644 --- a/src/library/scala/sys/process/ProcessBuilder.scala +++ b/src/library/scala/sys/process/ProcessBuilder.scala @@ -62,7 +62,7 @@ import ProcessBuilder._ * there's a few methods that create a new `ProcessBuilder` with a * pre-configured input or output. They are `#<`, `#>` and `#>>`, and may take * as input either another `ProcessBuilder` (like the pipe described above), or - * something else such as a `java.io.File` or a `java.lang.InputStream`. + * something else such as a `java.io.File` or a `java.io.InputStream`. * For example: * {{{ * new URL("http://databinder.net/dispatch/About") #> "grep JSON" #>> new File("About_JSON") ! diff --git a/src/library/scala/util/Sorting.scala b/src/library/scala/util/Sorting.scala index 276e157f55..2e021ad9d9 100644 --- a/src/library/scala/util/Sorting.scala +++ b/src/library/scala/util/Sorting.scala @@ -141,14 +141,14 @@ object Sorting { var done = false while (!done) { while (b <= c && x(b) <= v) { - if (x(b) == v) { + if (x(b) equiv v) { swap(a, b) a += 1 } b += 1 } while (c >= b && x(c) >= v) { - if (x(c) == v) { + if (x(c) equiv v) { swap(c, d) d -= 1 } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 0dfcf06874..e15b33e5d7 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -801,9 +801,14 @@ trait Symbols extends api.Symbols { self: SymbolTable => isConstructor && !isPrimaryConstructor /** Is this symbol a synthetic apply or unapply method in a companion object of a case class? */ + // xeno-by: why this obscure use of the CASE flag? why not simply compare name with nme.apply and nme.unapply? final def isCaseApplyOrUnapply = isMethod && isCase && isSynthetic + /** Is this symbol a synthetic copy method in a case class? */ + final def isCaseCopy = + isMethod && owner.isCase && isSynthetic && name == nme.copy + /** Is this symbol a trait which needs an implementation class? */ final def needsImplClass = ( isTrait diff --git a/test/files/run/macro-default-params.check b/test/files/run/macro-default-params.check new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/test/files/run/macro-default-params.check @@ -0,0 +1 @@ +0 diff --git a/test/files/run/macro-default-params/Macros_1.scala b/test/files/run/macro-default-params/Macros_1.scala new file mode 100644 index 0000000000..47780ea4b8 --- /dev/null +++ b/test/files/run/macro-default-params/Macros_1.scala @@ -0,0 +1,27 @@ +import scala.language.experimental.macros +import scala.reflect.macros.WhiteboxContext + +object Macros { + def id[A]: A = null.asInstanceOf[A] + + def foo: Any = macro impl + def impl(c: WhiteboxContext): c.Tree = { + import c.universe._ + import Flag._ + + lazy val tpe = TypeTree(typeOf[Int]) + + /* If we used this line instead, it would work! */ + // lazy val tpe = tq"Int" + + lazy val param: ValDef = { + val p1 = q"val a: ${tpe.duplicate} = Macros.id[${tpe.duplicate}]" + ValDef(Modifiers(DEFAULTPARAM), p1.name, p1.tpt, p1.rhs) + } + + q""" + class C { def f($param) = a } + println(new C().f()) + """ + } +} diff --git a/test/files/run/macro-default-params/Test_2.scala b/test/files/run/macro-default-params/Test_2.scala new file mode 100644 index 0000000000..5d19639cdd --- /dev/null +++ b/test/files/run/macro-default-params/Test_2.scala @@ -0,0 +1,3 @@ +object Test extends App { + Macros.foo +} diff --git a/test/files/run/mutable-anyrefmap.scala b/test/files/run/mutable-anyrefmap.scala deleted file mode 100644 index ff615d0daf..0000000000 --- a/test/files/run/mutable-anyrefmap.scala +++ /dev/null @@ -1,91 +0,0 @@ -object Test extends App { - - import scala.collection.mutable.HashMap; - import scala.collection.mutable.AnyRefMap; - - val keys = Array( - null, "perch", "herring", "salmon", "pike", "cod", "" - ) - - val rn = new scala.util.Random(42L) - var arm = AnyRefMap.empty[String, Int] - val hm = HashMap.empty[String, Int] - - def checkConsistent = hm.forall{ case (k,v) => arm.get(k).exists(_ == v) } - - assert { - (0 to 10000).forall{ i => - val k = keys(rn.nextInt(keys.length)) - if (rn.nextInt(100) < 2) arm = arm.clone() - if (rn.nextInt(100) < 5) arm.repack() - if (rn.nextBoolean) { - hm += ((k, i)) - rn.nextInt(6) match { - case 0 => arm += ((k, i)) - case 1 => arm += (k, i) - case 2 => arm(k) = i - case 3 => arm.put(k,i) - case 4 => arm ++= List((k,i)) - case _ => if (!arm.contains(k)) arm.getOrElseUpdate(k,i) - else arm += (k,i) - } - } - else { - hm -= k - rn.nextInt(2) match { - case 0 => arm -= k - case _ => arm --= List(k) - } - } - checkConsistent - } - } - - assert { - val mapped = - arm.map{ case (k,v) => (if (k==null) "" else k+k) -> v.toString } - mapped.getClass == arm.getClass - } - - assert { - val arm2 = new AnyRefMap[java.lang.Integer,Unit](2000000) - for (i <- 0 until 1000000) arm2(java.lang.Integer.valueOf(i)) = () - - arm2.size == 1000000 && - (0 to 1100000 by 100000).map(java.lang.Integer.valueOf).forall(i => (arm2 contains i) == i < 1000000) - } - - arm = AnyRefMap("heron" -> 22, "dove" -> 5, "budgie" -> 0) - - assert{ - var s = "" - arm.foreachKey(s += _) - - s.length == "herondovebudgie".length && - s.contains("heron") && - s.contains("dove") && - s.contains("budgie") - } - - assert{ var s = 0L; arm.foreachValue(s += _); s == 27L } - - assert { - val m2 = arm.mapValuesNow(_+2) - arm.transformValues(_+2) - m2 == arm && !(m2 eq arm) && (for ((_,v) <- arm) yield v).sum == 33L - } - - assert { - val arm2 = new AnyRefMap[String, String](x => if (x==null) "null" else x) - arm2 += ("cod" -> "fish", "Rarity" -> "unicorn") - val hm2 = (new HashMap[String,String]) ++= arm2 - - List(null, "cod", "sparrow", "Rarity").forall(i => - arm2.get(i) == hm2.get(i) && - arm2.getOrElse(i, "") == hm2.getOrElse(i, "") && - arm2(i) == hm2.get(i).getOrElse(if (i==null) "null" else i.toString) && - arm2.getOrNull(i) == hm2.get(i).orNull - ) - } -} - diff --git a/test/files/run/mutable-longmap.scala b/test/files/run/mutable-longmap.scala deleted file mode 100644 index 07fd80f6f0..0000000000 --- a/test/files/run/mutable-longmap.scala +++ /dev/null @@ -1,79 +0,0 @@ -object Test extends App { - - import scala.collection.mutable.HashMap; - import scala.collection.mutable.LongMap; - - val keys = Array( - Long.MinValue, Int.MinValue - 1L, Int.MinValue, -9127, -1, - 0, 1, 9127, Int.MaxValue, Long.MaxValue - ) - - val rn = new scala.util.Random(42L) - var lm = LongMap.empty[Long] - val hm = HashMap.empty[Long,Long] - - def checkConsistent = hm.forall{ case (k,v) => lm.get(k).exists(_ == v) } - - assert { - (0 to 10000).forall{ i => - val k = keys(rn.nextInt(keys.length)) - if (rn.nextInt(100) < 2) lm = lm.clone() - if (rn.nextInt(100) < 5) lm.repack() - if (rn.nextBoolean) { - hm += ((k, i)) - rn.nextInt(6) match { - case 0 => lm += ((k, i)) - case 1 => lm += (k, i) - case 2 => lm(k) = i - case 3 => lm.put(k,i) - case 4 => lm ++= List((k,i)) - case _ => if (!lm.contains(k)) lm.getOrElseUpdate(k,i) - else lm += (k,i) - } - } - else { - hm -= k - rn.nextInt(2) match { - case 0 => lm -= k - case _ => lm --= List(k) - } - } - checkConsistent - } - } - - assert { - lm.map{ case (k,v) => -k*k -> v.toString }.getClass == lm.getClass - } - - assert { - val lm2 = new LongMap[Unit](2000000) - for (i <- 0 until 1000000) lm2(i) = () - - lm2.size == 1000000 && - (0 to 1100000 by 100000).forall(i => (lm2 contains i) == i < 1000000) - } - - lm = LongMap(8L -> 22L, -5L -> 5L, Long.MinValue -> 0L) - - assert{ var s = 0L; lm.foreachKey(s += _); s == Long.MinValue + 3 } - assert{ var s = 0L; lm.foreachValue(s += _); s == 27L } - assert { - val m2 = lm.mapValuesNow(_+2) - lm.transformValues(_+2) - m2 == lm && !(m2 eq lm) && (for ((_,v) <- lm) yield v).sum == 33L - } - - assert { - val lm2 = new LongMap[String](_.toString) - lm2 += (5L -> "fish", 0L -> "unicorn") - val hm2 = (new HashMap[Long,String]) ++= lm2 - - List(Long.MinValue, 0L, 1L, 5L).forall(i => - lm2.get(i) == hm2.get(i) && - lm2.getOrElse(i, "") == hm2.getOrElse(i, "") && - lm2(i) == hm2.get(i).getOrElse(i.toString) && - lm2.getOrNull(i) == hm2.get(i).orNull - ) - } -} diff --git a/test/junit/scala/collection/ArraySortingTest.scala b/test/junit/scala/collection/ArraySortingTest.scala new file mode 100644 index 0000000000..4e54b39ce7 --- /dev/null +++ b/test/junit/scala/collection/ArraySortingTest.scala @@ -0,0 +1,29 @@ +package scala.collection.mutable + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test + +/* Tests various maps by making sure they all agree on the same answers. */ +@RunWith(classOf[JUnit4]) +class ArraySortingTest { + + class CantSortMe(val i: Int) { + override def equals(a: Any) = throw new IllegalArgumentException("I cannot be equalled!") + } + + object CanOrder extends Ordering[CantSortMe] { + def compare(a: CantSortMe, b: CantSortMe) = a.i compare b.i + } + + // Tests SI-7837 + @Test + def sortByTest() { + val test = Array(1,2,3,4,1,3,5,7,1,4,8,1,1,1,1) + val cant = test.map(i => new CantSortMe(i)) + java.util.Arrays.sort(test) + scala.util.Sorting.quickSort(cant)(CanOrder) + assert( test(6) == 1 ) + assert( (test,cant).zipped.forall(_ == _.i) ) + } +} diff --git a/test/junit/scala/collection/SetMapConsistencyTest.scala b/test/junit/scala/collection/SetMapConsistencyTest.scala new file mode 100644 index 0000000000..c62b074483 --- /dev/null +++ b/test/junit/scala/collection/SetMapConsistencyTest.scala @@ -0,0 +1,479 @@ +package scala.collection + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.collection.{mutable => cm, immutable => ci} +import scala.collection.JavaConverters._ + +/* Tests various maps by making sure they all agree on the same answers. */ +@RunWith(classOf[JUnit4]) +class SetMapConsistencyTest { + + trait MapBox[A] { + protected def oor(s: String, n: Int) = throw new IllegalArgumentException(s"Out of range for $s: $n") + def title: String + def adders: Int + def add(n: Int, a: A, v: Int): Unit + def subbers: Int + def sub(n: Int, a: A): Unit + def getters: Int + def get(n: Int, a: A): Int + def fiddlers: Int + def fiddle(n: Int): Unit + def keys: Iterator[A] + def has(a: A): Boolean + } + + + // Mutable map wrappers + + class BoxMutableMap[A, M <: cm.Map[A, Int]](m0: M, title0: String) extends MapBox[A] { + var m = m0 + def title = title0 + def adders = 5 + def add(n: Int, a: A, v: Int) { n match { + case 0 => m += ((a, v)) + case 1 => m(a) = v + case 2 => m.put(a, v) + case 3 => m = (m + ((a, v))).asInstanceOf[M] + case 4 => m = (m ++ List((a, v))).asInstanceOf[M] + case _ => oor("add", n) + }} + def subbers: Int = 3 + def sub(n: Int, a: A) { n match { + case 0 => m -= a + case 1 => m = (m - a).asInstanceOf[M] + case 2 => m = m.filter(_._1 != a).asInstanceOf[M] + case _ => oor("sub", n) + }} + def getters: Int = 3 + def get(n: Int, a: A) = n match { + case 0 => m.get(a).getOrElse(-1) + case 1 => if (m contains a) m(a) else -1 + case 2 => m.getOrElse(a, -1) + case _ => oor("get", n) + } + def fiddlers: Int = 0 + def fiddle(n: Int) { oor("fiddle", n) } + def keys = m.keysIterator + def has(a: A) = m contains a + override def toString = m.toString + } + + def boxMlm[A] = new BoxMutableMap[A, cm.ListMap[A, Int]](new cm.ListMap[A, Int], "mutable.ListMap") + + def boxMhm[A] = new BoxMutableMap[A, cm.HashMap[A, Int]](new cm.HashMap[A, Int], "mutable.HashMap") + + def boxMohm[A] = new BoxMutableMap[A, cm.OpenHashMap[A, Int]](new cm.OpenHashMap[A, Int], "mutable.OpenHashMap") + + def boxMarm[A <: AnyRef] = new BoxMutableMap[A, cm.AnyRefMap[A, Int]](new cm.AnyRefMap[A, Int](_ => -1), "mutable.AnyRefMap") { + private def arm: cm.AnyRefMap[A, Int] = m.asInstanceOf[cm.AnyRefMap[A, Int]] + override def adders = 3 + override def subbers = 1 + override def getters: Int = 4 + override def get(n: Int, a: A) = n match { + case 0 => m.get(a).getOrElse(-1) + case 1 => m(a) + case 2 => m.getOrElse(a, -1) + case 3 => val x = arm.getOrNull(a); if (x==0 && !(arm contains a)) -1 else x + case _ => oor("get", n) + } + override def fiddlers = 2 + override def fiddle(n: Int) { n match { + case 0 => m = arm.clone + case 1 => arm.repack + case _ => oor("fiddle", n) + }} + } + + def boxMjm = new BoxMutableMap[Long, cm.LongMap[Int]](new cm.LongMap[Int](_ => -1), "mutable.LongMap") { + private def lm: cm.LongMap[Int] = m.asInstanceOf[cm.LongMap[Int]] + override def adders = 3 + override def subbers = 1 + override def getters: Int = 4 + override def get(n: Int, a: Long) = n match { + case 0 => m.get(a).getOrElse(-1) + case 1 => m(a) + case 2 => m.getOrElse(a, -1) + case 3 => val x = lm.getOrNull(a); if (x==0 && !(lm contains a)) -1 else x + case _ => oor("get", n) + } + override def fiddlers = 2 + override def fiddle(n: Int) { n match { + case 0 => m = lm.clone + case 1 => lm.repack + case _ => oor("fiddle", n) + }} + } + + def boxJavaM[A] = new BoxMutableMap[A, cm.Map[A, Int]]((new java.util.HashMap[A, Int]).asScala, "java.util.HashMap") { + override def adders = 3 + override def subbers = 1 + } + + + // Immutable map wrappers + + class BoxImmutableMap[A, M <: ci.Map[A, Int]](m0: M, title0: String) extends MapBox[A] { + var m = m0 + def title = title0 + def adders = 2 + def add(n: Int, a: A, v: Int) { n match { + case 0 => m = (m + ((a, v))).asInstanceOf[M] + case 1 => m = (m ++ List((a, v))).asInstanceOf[M] + case _ => oor("add", n) + }} + def subbers: Int = 2 + def sub(n: Int, a: A) { n match { + case 0 => m = (m - a).asInstanceOf[M] + case 1 => m = m.filter(_._1 != a).asInstanceOf[M] + case _ => oor("sub", n) + }} + def getters: Int = 3 + def get(n: Int, a: A) = n match { + case 0 => m.get(a).getOrElse(-1) + case 1 => if (m contains a) m(a) else -1 + case 2 => m.getOrElse(a, -1) + case _ => oor("get", n) + } + def fiddlers: Int = 0 + def fiddle(n: Int) { oor("fiddle", n) } + def keys = m.keysIterator + def has(a: A) = m contains a + override def toString = m.toString + } + + def boxIhm[A] = new BoxImmutableMap[A, ci.HashMap[A,Int]](new ci.HashMap[A, Int], "immutable.HashMap") + + def boxIim = new BoxImmutableMap[Int, ci.IntMap[Int]](ci.IntMap.empty[Int], "immutable.IntMap") + + def boxIjm = new BoxImmutableMap[Long, ci.LongMap[Int]](ci.LongMap.empty[Int], "immutable.LongMap") + + def boxIlm[A] = new BoxImmutableMap[A, ci.ListMap[A, Int]](new ci.ListMap[A, Int], "immutable.ListMap") + + def boxItm[A: Ordering] = new BoxImmutableMap[A, ci.TreeMap[A, Int]](new ci.TreeMap[A, Int], "immutable.TreeMap") + + + // Mutable set wrappers placed into the same framework (everything returns 0) + + class BoxMutableSet[A, M <: cm.Set[A]](s0: M, title0: String) extends MapBox[A] { + protected var m = s0 + def title = title0 + def adders = 5 + def add(n: Int, a: A, v: Int) { n match { + case 0 => m += a + case 1 => m(a) = true + case 2 => m add a + case 3 => m = (m + a).asInstanceOf[M] + case 4 => m = (m ++ List(a)).asInstanceOf[M] + case _ => oor("add", n) + }} + def subbers: Int = 3 + def sub(n: Int, a: A) { n match { + case 0 => m -= a + case 1 => m = (m - a).asInstanceOf[M] + case 2 => m = m.filter(_ != a).asInstanceOf[M] + case _ => oor("sub", n) + }} + def getters: Int = 1 + def get(n: Int, a: A) = if (m(a)) 0 else -1 + def fiddlers: Int = 0 + def fiddle(n: Int) { oor("fiddle", n) } + def keys = m.iterator + def has(a: A) = m(a) + override def toString = m.toString + } + + def boxMbs = new BoxMutableSet[Int, cm.BitSet](new cm.BitSet, "mutable.BitSet") + + def boxMhs[A] = new BoxMutableSet[A, cm.HashSet[A]](new cm.HashSet[A], "mutable.HashSet") + + def boxJavaS[A] = new BoxMutableSet[A, cm.Set[A]]((new java.util.HashSet[A]).asScala, "java.util.HashSet") { + override def adders = 3 + override def subbers = 1 + } + + + // Immutable set wrappers placed into the same framework (everything returns 0) + + class BoxImmutableSet[A, M <: ci.Set[A]](s0: M, title0: String) extends MapBox[A] { + protected var m = s0 + def title = title0 + def adders = 2 + def add(n: Int, a: A, v: Int) { n match { + case 0 => m = (m + a).asInstanceOf[M] + case 1 => m = (m ++ List(a)).asInstanceOf[M] + case _ => oor("add", n) + }} + def subbers: Int = 2 + def sub(n: Int, a: A) { n match { + case 0 => m = (m - a).asInstanceOf[M] + case 1 => m = m.filter(_ != a).asInstanceOf[M] + case _ => oor("sub", n) + }} + def getters: Int = 1 + def get(n: Int, a: A) = if (m(a)) 0 else -1 + def fiddlers: Int = 0 + def fiddle(n: Int) { oor("fiddle", n) } + def keys = m.iterator + def has(a: A) = m(a) + override def toString = m.toString + } + + def boxIbs = new BoxImmutableSet[Int, ci.BitSet](ci.BitSet.empty, "immutable.BitSet") + + def boxIhs[A] = new BoxImmutableSet[A, ci.HashSet[A]](ci.HashSet.empty[A], "mutable.HashSet") + + def boxIls[A] = new BoxImmutableSet[A, ci.ListSet[A]](ci.ListSet.empty[A], "mutable.ListSet") + + def boxIts[A: Ordering] = new BoxImmutableSet[A, ci.TreeSet[A]](ci.TreeSet.empty[A], "mutable.TreeSet") + + + // Random operations on maps + def churn[A](map1: MapBox[A], map2: MapBox[A], keys: Array[A], n: Int = 1000, seed: Int = 42, valuer: Int => Int = identity) = { + def check = map1.keys.forall(map2 has _) && map2.keys.forall(map1 has _) + val rn = new scala.util.Random(seed) + var what = new StringBuilder + what ++= "creation" + for (i <- 0 until n) { + if (!check) { + val temp = map2 match { + case b: BoxImmutableMap[_, _] => b.m match { + case hx: ci.HashMap.HashTrieMap[_,_] => + val h = hx.asInstanceOf[ci.HashMap.HashTrieMap[A, Int]] + Some((h.bitmap.toHexString, h.elems.mkString, h.size)) + case _ => None + } + case _ => None + } + throw new Exception(s"Disagreement after ${what.result} between ${map1.title} and ${map2.title} because ${map1.keys.map(map2 has _).mkString(",")} ${map2.keys.map(map1 has _).mkString(",")} at step $i:\n$map1\n$map2\n$temp") + } + what ++= " (%d) ".format(i) + if (rn.nextInt(10)==0) { + + if (map1.fiddlers > 0) map1.fiddle({ + val n = rn.nextInt(map1.fiddlers) + what ++= ("f"+n) + n + }) + if (map2.fiddlers > 0) map2.fiddle({ + val n = rn.nextInt(map2.fiddlers) + what ++= ("F"+n) + n + }) + } + if (rn.nextBoolean) { + val idx = rn.nextInt(keys.length) + val key = keys(rn.nextInt(keys.length)) + val n1 = rn.nextInt(map1.adders) + val n2 = rn.nextInt(map2.adders) + what ++= "+%s(%d,%d)".format(key,n1,n2) + map1.add(n1, key, valuer(idx)) + map2.add(n2, key, valuer(idx)) + } + else { + val n = rn.nextInt(keys.length) + val key = keys(n) + val n1 = rn.nextInt(map1.subbers) + val n2 = rn.nextInt(map2.subbers) + what ++= "-%s(%d,%d)".format(key, n1, n2) + //println(s"- $key") + map1.sub(n1, key) + map2.sub(n2, key) + } + val j = rn.nextInt(keys.length) + val gn1 = rn.nextInt(map1.getters) + val gn2 = rn.nextInt(map2.getters) + val g1 = map1.get(gn1, keys(j)) + val g2 = map2.get(gn2, keys(j)) + if (g1 != g2) { + val temp = map2 match { + case b: BoxImmutableMap[_, _] => b.m match { + case hx: ci.HashMap.HashTrieMap[_,_] => + val h = hx.asInstanceOf[ci.HashMap.HashTrieMap[A, Int]] + val y = (ci.HashMap.empty[A, Int] ++ h).asInstanceOf[ci.HashMap.HashTrieMap[A, Int]] + Some(((h.bitmap.toHexString, h.elems.mkString, h.size),(y.bitmap.toHexString, y.elems.mkString, y.size))) + case _ => None + } + case _ => None + } + throw new Exception(s"Disagreement after ${what.result} between ${map1.title} and ${map2.title} on get of ${keys(j)} (#$j) on step $i: $g1 != $g2 using methods $gn1 and $gn2 resp.; in full\n$map1\n$map2\n$temp") + } + } + true + } + + + // Actual tests + val smallKeys = Array(0, 1, 42, 9127) + val intKeys = smallKeys ++ Array(-1, Int.MaxValue, Int.MinValue, -129385) + val longKeys = intKeys.map(_.toLong) ++ Array(Long.MaxValue, Long.MinValue, 1397198789151L, -41402148014L) + val stringKeys = intKeys.map(_.toString) ++ Array("", null) + val anyKeys = stringKeys.filter(_ != null) ++ Array(0L) ++ Array(true) ++ Array(math.Pi) + + @Test + def churnIntMaps() { + val maps = Array[() => MapBox[Int]]( + () => boxMlm[Int], () => boxMhm[Int], () => boxMohm[Int], () => boxJavaM[Int], + () => boxIim, () => boxIhm[Int], () => boxIlm[Int], () => boxItm[Int] + ) + assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), intKeys, 2000) } ) + } + + @Test + def churnLongMaps() { + val maps = Array[() => MapBox[Long]]( + () => boxMjm, () => boxIjm, () => boxJavaM[Long], + () => boxMlm[Long], () => boxMhm[Long], () => boxMohm[Long], () => boxIhm[Long], () => boxIlm[Long] + ) + assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), longKeys, 10000) } ) + } + + @Test + def churnStringMaps() { + // Note: OpenHashMap and TreeMap won't store null, so skip strings + val maps = Array[() => MapBox[String]]( + () => boxMlm[String], () => boxMhm[String], () => boxMarm[String], () => boxJavaM[String], + () => boxIhm[String], () => boxIlm[String] + ) + assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), stringKeys, 5000) } ) + } + + @Test + def churnAnyMaps() { + val maps = Array[() => MapBox[Any]]( + () => boxMlm[Any], () => boxMhm[Any], () => boxMohm[Any], () => boxJavaM[Any], () => boxIhm[Any], () => boxIlm[Any] + ) + assert( maps.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), anyKeys, 10000) } ) + } + + @Test + def churnIntSets() { + val sets = Array[() => MapBox[Int]]( + () => boxMhm[Int], () => boxIhm[Int], () => boxJavaS[Int], + () => boxMbs, () => boxMhs[Int], () => boxIbs, () => boxIhs[Int], () => boxIls[Int], () => boxIts[Int] + ) + assert( sets.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), smallKeys, 1000, valuer = _ => 0) } ) + } + + @Test + def churnAnySets() { + val sets = Array[() => MapBox[Any]]( + () => boxMhm[Any], () => boxIhm[Any], () => boxJavaS[Any], + () => boxMhs[Any], () => boxIhs[Any], () => boxIls[Any] + ) + assert( sets.sliding(2).forall{ ms => churn(ms(0)(), ms(1)(), anyKeys, 10000, valuer = _ => 0) } ) + } + + @Test + def extraMutableLongMapTests() { + import cm.{LongMap, HashMap} + var lm = LongMap.empty[Long] + longKeys.zipWithIndex.foreach{ case (k,i) => lm(k) = i } + assert{ lm.map{ case (k,v) => -k*k -> v.toString }.getClass == lm.getClass } + + assert { + val lm2 = new LongMap[Unit](2000000) + for (i <- 0 until 1000000) lm2(i) = () + + lm2.size == 1000000 && + (0 to 1100000 by 100000).forall(i => (lm2 contains i) == i < 1000000) + } + + lm = LongMap(8L -> 22L, -5L -> 5L, Long.MinValue -> 0L) + + assert{ var s = 0L; lm.foreachKey(s += _); s == Long.MinValue + 3 } + assert{ var s = 0L; lm.foreachValue(s += _); s == 27L } + assert { + val m2 = lm.mapValuesNow(_+2) + lm.transformValues(_+2) + m2 == lm && !(m2 eq lm) && (for ((_,v) <- lm) yield v).sum == 33L + } + + assert { + val lm2 = new LongMap[String](_.toString) + lm2 += (5L -> "fish", 0L -> "unicorn") + val hm2 = (new HashMap[Long,String]) ++= lm2 + List(Long.MinValue, 0L, 1L, 5L).forall(i => + lm2.get(i) == hm2.get(i) && + lm2.getOrElse(i, "") == hm2.getOrElse(i, "") && + lm2(i) == hm2.get(i).getOrElse(i.toString) && + lm2.getOrNull(i) == hm2.get(i).orNull + ) + } + } + + @Test + def extraMutableAnyRefMapTests() { + import cm.{AnyRefMap, HashMap} + var arm = AnyRefMap.empty[String, Int] + stringKeys.zipWithIndex.foreach{ case (k,i) => arm(k) = i } + + assert{ arm.map{ case (k,v) => (if (k==null) "" else k+k) -> v.toString }.getClass == arm.getClass } + + assert { + val arm2 = new AnyRefMap[java.lang.Integer,Unit](2000000) + for (i <- 0 until 1000000) arm2(java.lang.Integer.valueOf(i)) = () + arm2.size == 1000000 && + (0 to 1100000 by 100000).map(java.lang.Integer.valueOf).forall(i => (arm2 contains i) == i < 1000000) + } + + arm = AnyRefMap("heron" -> 22, "dove" -> 5, "budgie" -> 0) + + assert{ + var s = "" + arm.foreachKey(s += _) + s.length == "herondovebudgie".length && + s.contains("heron") && + s.contains("dove") && + s.contains("budgie") + } + + assert{ var s = 0L; arm.foreachValue(s += _); s == 27L } + + assert { + val m2 = arm.mapValuesNow(_+2) + arm.transformValues(_+2) + m2 == arm && !(m2 eq arm) && (for ((_,v) <- arm) yield v).sum == 33L + } + + assert { + val arm2 = new AnyRefMap[String, String](x => if (x==null) "null" else x) + arm2 += ("cod" -> "fish", "Rarity" -> "unicorn") + val hm2 = (new HashMap[String,String]) ++= arm2 + List(null, "cod", "sparrow", "Rarity").forall(i => + arm2.get(i) == hm2.get(i) && + arm2.getOrElse(i, "") == hm2.getOrElse(i, "") && + arm2(i) == hm2.get(i).getOrElse(if (i==null) "null" else i.toString) && + arm2.getOrNull(i) == hm2.get(i).orNull + ) + } + } + + @Test + def extraFilterTests() { + type M = scala.collection.Map[Int, Boolean] + val manyKVs = (0 to 1000).map(i => i*i*i).map(x => x -> ((x*x*x) < 0)) + val rn = new scala.util.Random(42) + def mhm: M = { val m = new cm.HashMap[Int, Boolean]; m ++= manyKVs; m } + def mohm: M = { val m = new cm.OpenHashMap[Int, Boolean]; m ++= manyKVs; m } + def ihm: M = ci.HashMap.empty[Int, Boolean] ++ manyKVs + val densities = List(0, 0.05, 0.2, 0.5, 0.8, 0.95, 1) + def repeat = rn.nextInt(100) < 33 + def pick(m: M, density: Double) = m.keys.filter(_ => rn.nextDouble < density).toSet + def test: Boolean = { + for (i <- 0 to 100) { + var ms = List(mhm, mohm, ihm) + do { + val density = densities(rn.nextInt(densities.length)) + val keep = pick(ms.head, density) + ms = ms.map(_.filter(keep contains _._1)) + if (!ms.sliding(2).forall(s => s(0) == s(1))) return false + } while (repeat) + } + true + } + assert(test) + } +} diff --git a/test/junit/scala/math/NumericTest.scala b/test/junit/scala/math/NumericTest.scala new file mode 100644 index 0000000000..4f0657f471 --- /dev/null +++ b/test/junit/scala/math/NumericTest.scala @@ -0,0 +1,18 @@ + + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class NumericTest { + + /* Test for SI-8102 */ + @Test + def testAbs { + assertTrue(-0.0.abs equals 0.0) + assertTrue(-0.0f.abs equals 0.0f) + } +} + |