diff options
77 files changed, 1634 insertions, 316 deletions
@@ -346,6 +346,7 @@ lazy val library = configureAsSubproject(project) base ** "*.txt" pair relativeTo(base) }, Osgi.headers += "Import-Package" -> "sun.misc;resolution:=optional, *", + Osgi.jarlist := true, fixPom( "/project/name" -> <name>Scala Library</name>, "/project/description" -> <description>Standard library for the Scala Programming Language</description>, @@ -412,13 +413,15 @@ lazy val compiler = configureAsSubproject(project) scalacOptions in Compile in doc ++= Seq( "-doc-root-content", (sourceDirectory in Compile).value + "/rootdoc.txt" ), - Osgi.headers += + Osgi.headers ++= Seq( "Import-Package" -> ("jline.*;resolution:=optional," + "org.apache.tools.ant.*;resolution:=optional," + "scala.util.parsing.*;version=\"${range;[====,====];"+versionNumber("scala-parser-combinators")+"}\";resolution:=optional," + "scala.xml.*;version=\"${range;[====,====];"+versionNumber("scala-xml")+"}\";resolution:=optional," + "scala.*;version=\"${range;[==,=+);${ver}}\"," + "*"), + "Class-Path" -> "scala-reflect.jar scala-library.jar" + ), // Generate the ScriptEngineFactory service definition. The ant build does this when building // the JAR but sbt has no support for it and it is easier to do as a resource generator: generateServiceProviderResources("javax.script.ScriptEngineFactory" -> "scala.tools.nsc.interpreter.IMain$Factory"), @@ -273,6 +273,10 @@ TODO: <dependency groupId="org.pantsbuild" artifactId="jarjar" version="1.6.0"/> </artifact:dependencies> + <artifact:dependencies pathId="jarlister.classpath"> + <dependency groupId="com.github.rjolly" artifactId="jarlister_2.11" version="1.0"/> + </artifact:dependencies> + <!-- JUnit --> <property name="junit.version" value="4.12"/> <artifact:dependencies pathId="junit.classpath" filesetId="junit.fileset"> @@ -787,6 +791,11 @@ TODO: <path refid="aux.libs"/> </path> + <path id="pack.lib.path"> + <pathelement location="${library.jar}"/> + <path refid="jarlister.classpath"/> + </path> + <path id="pack.bin.tool.path"> <pathelement location="${library.jar}"/> <pathelement location="${xml.jar}"/> @@ -1138,7 +1147,10 @@ TODO: <!-- =========================================================================== PACKED QUICK BUILD (PACK) ============================================================================ --> - <target name="pack.lib" depends="quick.lib"> <staged-pack project="library"/></target> + <target name="pack.lib" depends="quick.lib"> <staged-pack project="library"/> + <taskdef resource="scala/tools/ant/antlib.xml" classpathref="pack.lib.path"/> + <jarlister file="${library.jar}"/> + </target> <target name="pack.reflect" depends="quick.reflect"> <staged-pack project="reflect"/> </target> diff --git a/project/Osgi.scala b/project/Osgi.scala index 36803c0e44..9b00379760 100644 --- a/project/Osgi.scala +++ b/project/Osgi.scala @@ -1,6 +1,7 @@ import aQute.bnd.osgi.Builder import aQute.bnd.osgi.Constants._ import java.util.Properties +import java.util.jar.Attributes import sbt._ import sbt.Keys._ import collection.JavaConverters._ @@ -16,6 +17,7 @@ object Osgi { val bundleName = SettingKey[String]("osgiBundleName", "The Bundle-Name for the manifest.") val bundleSymbolicName = SettingKey[String]("osgiBundleSymbolicName", "The Bundle-SymbolicName for the manifest.") val headers = SettingKey[Seq[(String, String)]]("osgiHeaders", "Headers and processing instructions for BND.") + val jarlist = SettingKey[Boolean]("osgiJarlist", "List classes in manifest.") def settings: Seq[Setting[_]] = Seq( bundleName := description.value, @@ -33,9 +35,10 @@ object Osgi { "-eclipse" -> "false" ) }, + jarlist := false, bundle <<= Def.task { val res = (products in Compile in packageBin).value - bundleTask(headers.value.toMap, (products in Compile in packageBin).value, + bundleTask(headers.value.toMap, jarlist.value, (products in Compile in packageBin).value, (artifactPath in (Compile, packageBin)).value, res, streams.value) }, packagedArtifact in (Compile, packageBin) <<= (artifact in (Compile, packageBin), bundle).identityMap, @@ -48,7 +51,7 @@ object Osgi { ) ) - def bundleTask(headers: Map[String, String], fullClasspath: Seq[File], artifactPath: File, + def bundleTask(headers: Map[String, String], jarlist: Boolean, fullClasspath: Seq[File], artifactPath: File, resourceDirectories: Seq[File], streams: TaskStreams): File = { val log = streams.log val builder = new Builder @@ -63,6 +66,12 @@ object Osgi { builder.getWarnings.asScala.foreach(s => log.warn(s"bnd: $s")) builder.getErrors.asScala.foreach(s => log.error(s"bnd: $s")) IO.createDirectory(artifactPath.getParentFile) + if (jarlist) { + val entries = jar.getManifest.getEntries + for ((name, resource) <- jar.getResources.asScala if name.endsWith(".class")) { + entries.put(name, new Attributes) + } + } jar.write(artifactPath) artifactPath } diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index 930163af36..e9d1dfe4d2 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -575,8 +575,6 @@ class Scalac extends ScalaMatchingTask with ScalacShared { settings.classpath.value = asString(getClasspath) if (!sourcepath.isEmpty) settings.sourcepath.value = asString(getSourcepath) - else if (origin.get.size() > 0) - settings.sourcepath.value = origin.get.list()(0) if (!bootclasspath.isEmpty) settings.bootclasspath.value = asString(getBootclasspath) if (!extdirs.isEmpty) settings.extdirs.value = asString(getExtdirs) diff --git a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala index 24496fa013..bab612bad5 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala @@ -79,6 +79,7 @@ Other startup options: -howtorun what to run <script|object|jar|guess> (default: guess) -i <file> preload <file> before starting the repl + -I <file> preload <file>, enforcing line-by-line interpretation -e <string> execute <string> as if entered in the repl -save save the compiled script in a jar for future use -nc no compilation daemon: do not use the fsc offline compiler diff --git a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala index c82ed68da8..113c02e558 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala @@ -21,9 +21,15 @@ class GenericRunnerSettings(error: String => Unit) extends Settings(error) { val loadfiles = MultiStringSetting( + "-I", + "file", + "load a file line-by-line") + + val pastefiles = + MultiStringSetting( "-i", "file", - "load a file (assumes the code is given interactively)") + "paste a file") val execute = StringSetting( diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index f59deafe1b..cf66e0a7dc 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -364,12 +364,15 @@ self => val stmts = parseStats() def mainModuleName = newTermName(settings.script.value) + /* If there is only a single object template in the file and it has a * suitable main method, we will use it rather than building another object * around it. Since objects are loaded lazily the whole script would have * been a no-op, so we're not taking much liberty. */ - def searchForMain(): Option[Tree] = { + def searchForMain(): Tree = { + import PartialFunction.cond + /* Have to be fairly liberal about what constitutes a main method since * nothing has been typed yet - for instance we can't assume the parameter * type will look exactly like "Array[String]" as it could have been renamed @@ -379,11 +382,15 @@ self => case DefDef(_, nme.main, Nil, List(_), _, _) => true case _ => false } - /* For now we require there only be one top level object. */ + def isApp(t: Tree) = t match { + case Template(parents, _, _) => parents.exists(cond(_) { case Ident(tpnme.App) => true }) + case _ => false + } + /* We allow only one main module. */ var seenModule = false - val newStmts = stmts collect { - case t @ Import(_, _) => t - case md @ ModuleDef(mods, name, template) if !seenModule && (md exists isMainMethod) => + var disallowed = EmptyTree: Tree + val newStmts = stmts.map { + case md @ ModuleDef(mods, name, template) if !seenModule && (isApp(template) || md.exists(isMainMethod)) => seenModule = true /* This slightly hacky situation arises because we have no way to communicate * back to the scriptrunner what the name of the program is. Even if we were @@ -394,50 +401,63 @@ self => */ if (name == mainModuleName) md else treeCopy.ModuleDef(md, mods, mainModuleName, template) - case _ => + case md @ ModuleDef(_, _, _) => md + case cd @ ClassDef(_, _, _, _) => cd + case t @ Import(_, _) => t + case t => /* If we see anything but the above, fail. */ - return None + if (disallowed.isEmpty) disallowed = t + EmptyTree + } + if (disallowed.isEmpty) makeEmptyPackage(0, newStmts) + else { + if (seenModule) + warning(disallowed.pos.point, "Script has a main object but statement is disallowed") + EmptyTree } - Some(makeEmptyPackage(0, newStmts)) } - if (mainModuleName == newTermName(ScriptRunner.defaultScriptMain)) - searchForMain() foreach { return _ } + def mainModule: Tree = + if (mainModuleName == newTermName(ScriptRunner.defaultScriptMain)) searchForMain() else EmptyTree - /* Here we are building an AST representing the following source fiction, - * where `moduleName` is from -Xscript (defaults to "Main") and <stmts> are - * the result of parsing the script file. - * - * {{{ - * object moduleName { - * def main(args: Array[String]): Unit = - * new AnyRef { - * stmts - * } - * } - * }}} - */ - def emptyInit = DefDef( - NoMods, - nme.CONSTRUCTOR, - Nil, - ListOfNil, - TypeTree(), - Block(List(Apply(gen.mkSuperInitCall, Nil)), literalUnit) - ) - - // def main - def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String))) - def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.args, mainParamType, EmptyTree)) - def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), gen.mkAnonymousNew(stmts)) - - // object Main - def moduleName = newTermName(ScriptRunner scriptMain settings) - def moduleBody = Template(atInPos(scalaAnyRefConstr) :: Nil, noSelfType, List(emptyInit, mainDef)) - def moduleDef = ModuleDef(NoMods, moduleName, moduleBody) - - // package <empty> { ... } - makeEmptyPackage(0, moduleDef :: Nil) + def repackaged: Tree = { + /* Here we are building an AST representing the following source fiction, + * where `moduleName` is from -Xscript (defaults to "Main") and <stmts> are + * the result of parsing the script file. + * + * {{{ + * object moduleName { + * def main(args: Array[String]): Unit = + * new AnyRef { + * stmts + * } + * } + * }}} + */ + def emptyInit = DefDef( + NoMods, + nme.CONSTRUCTOR, + Nil, + ListOfNil, + TypeTree(), + Block(List(Apply(gen.mkSuperInitCall, Nil)), literalUnit) + ) + + // def main + def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String))) + def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.args, mainParamType, EmptyTree)) + def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), gen.mkAnonymousNew(stmts)) + + // object Main + def moduleName = newTermName(ScriptRunner scriptMain settings) + def moduleBody = Template(atInPos(scalaAnyRefConstr) :: Nil, noSelfType, List(emptyInit, mainDef)) + def moduleDef = ModuleDef(NoMods, moduleName, moduleBody) + + // package <empty> { ... } + makeEmptyPackage(0, moduleDef :: Nil) + } + + mainModule orElse repackaged } /* --------------- PLACEHOLDERS ------------------------------------------- */ diff --git a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala index 00771b6b8c..dfd5b07a3b 100644 --- a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala +++ b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala @@ -61,8 +61,8 @@ abstract class ScalaPrimitives { final val NE = 43 // x != y final val LT = 44 // x < y final val LE = 45 // x <= y - final val GE = 46 // x > y - final val GT = 47 // x >= y + final val GT = 46 // x > y + final val GE = 47 // x >= y // Boolean unary operations final val ZNOT = 50 // !x diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 5d152ef0e8..d7106ae908 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -1110,22 +1110,19 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } /* Emit code to compare the two top-most stack values using the 'op' operator. */ - private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) { - if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump) + private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) { + if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated) else { if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT bc.emitIF_ICMP(op, success) } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) bc.emitIF_ACMP(op, success) } else { + def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE (tk: @unchecked) match { case LONG => emit(asm.Opcodes.LCMP) - case FLOAT => - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG) - else emit(asm.Opcodes.FCMPL) - case DOUBLE => - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) - else emit(asm.Opcodes.DCMPL) + case FLOAT => emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL) + case DOUBLE => emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL) } bc.emitIF(op, success) } @@ -1134,8 +1131,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } /* Emits code to compare (and consume) stack-top and zero using the 'op' operator */ - private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) { - if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump) + private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) { + if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated) else { if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT bc.emitIF(op, success) @@ -1145,18 +1142,17 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case TestOp.NE => bc emitIFNONNULL success } } else { + def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE (tk: @unchecked) match { case LONG => emit(asm.Opcodes.LCONST_0) emit(asm.Opcodes.LCMP) case FLOAT => emit(asm.Opcodes.FCONST_0) - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG) - else emit(asm.Opcodes.FCMPL) + emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL) case DOUBLE => emit(asm.Opcodes.DCONST_0) - if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) - else emit(asm.Opcodes.DCMPL) + emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL) } bc.emitIF(op, success) } @@ -1171,8 +1167,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case scalaPrimitives.NE => TestOp.NE case scalaPrimitives.LT => TestOp.LT case scalaPrimitives.LE => TestOp.LE - case scalaPrimitives.GE => TestOp.GE case scalaPrimitives.GT => TestOp.GT + case scalaPrimitives.GE => TestOp.GE } /** Some useful equality helpers. */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index a5744983b2..5a5747c81f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -1336,6 +1336,7 @@ object BCodeHelpers { } object TestOp { + // the order here / op numbers are important to get the correct result when calling opcodeIF val EQ = new TestOp(0) val NE = new TestOp(1) val LT = new TestOp(2) diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala index 9d02228ab5..f2237a0716 100644 --- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala +++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala @@ -192,8 +192,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { extensionDefs(currentOwner.companionModule) = new mutable.ListBuffer[Tree] currentOwner.primaryConstructor.makeNotPrivate(NoSymbol) // SI-7859 make param accessors accessible so the erasure can generate unbox operations. - val paramAccessors = currentOwner.info.decls.filter(sym => sym.isParamAccessor && sym.isMethod) - paramAccessors.foreach(_.makeNotPrivate(currentOwner)) + currentOwner.info.decls.foreach(sym => if (sym.isParamAccessor && sym.isMethod) sym.makeNotPrivate(currentOwner)) super.transform(tree) } else if (currentOwner.isStaticOwner) { super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index d519948a11..81a465ef2f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -539,8 +539,43 @@ trait ContextErrors { def NamedAndDefaultArgumentsNotSupportedForMacros(tree: Tree, fun: Tree) = NormalTypeError(tree, "macro applications do not support named and/or default arguments") - def TooManyArgsNamesDefaultsError(tree: Tree, fun: Tree) = - NormalTypeError(tree, "too many arguments for "+treeSymTypeMsg(fun)) + def TooManyArgsNamesDefaultsError(tree: Tree, fun: Tree, formals: List[Type], args: List[Tree], namelessArgs: List[Tree], argPos: Array[Int]) = { + val expected = formals.size + val supplied = args.size + // pick a caret. For f(k=1,i=2,j=3), argPos[0,-1,1] b/c `k=1` taken as arg0 + val excessive = { + val i = argPos.indexWhere(_ >= expected) + if (i < 0) tree else args(i min (supplied - 1)) + } + val msg = { + val badappl = { + val excess = supplied - expected + val target = treeSymTypeMsg(fun) + + if (expected == 0) s"no arguments allowed for nullary $target" + else if (excess < 3 && expected <= 5) s"too many arguments ($supplied) for $target" + else if (expected > 10) s"$supplied arguments but expected $expected for $target" + else { + val more = + if (excess == 1) "one more argument" + else if (excess > 0) s"$excess more arguments" + else "too many arguments" + s"$more than can be applied to $target" + } + } + val unknowns = (namelessArgs zip args) collect { + case (_: Assign, AssignOrNamedArg(Ident(name), _)) => name + } + val suppl = + unknowns.size match { + case 0 => "" + case 1 => s"\nNote that '${unknowns.head}' is not a parameter name of the invoked method." + case _ => unknowns.mkString("\nNote that '", "', '", "' are not parameter names of the invoked method.") + } + s"${badappl}${suppl}" + } + NormalTypeError(excessive, msg) + } // can it still happen? see test case neg/overloaded-unapply.scala def OverloadedUnapplyError(tree: Tree) = @@ -552,7 +587,7 @@ trait ContextErrors { def MultipleVarargError(tree: Tree) = NormalTypeError(tree, "when using named arguments, the vararg parameter has to be specified exactly once") - def ModuleUsingCompanionClassDefaultArgsErrror(tree: Tree) = + def ModuleUsingCompanionClassDefaultArgsError(tree: Tree) = NormalTypeError(tree, "module extending its companion class cannot use default constructor arguments") def NotEnoughArgsError(tree: Tree, fun: Tree, missing: List[Symbol]) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 8943ec810d..2773ee19cf 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -463,7 +463,7 @@ trait Namers extends MethodSynthesis { // opening up the package object on the classpath at all if one exists in source. if (m.isPackageObject) { val packageScope = m.enclosingPackageClass.rawInfo.decls - packageScope.filter(_.owner != m.enclosingPackageClass).toList.foreach(packageScope unlink _) + packageScope.foreach(mem => if (mem.owner != m.enclosingPackageClass) packageScope unlink mem) } updatePosFlags(m, tree.pos, moduleFlags) setPrivateWithin(tree, m) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 2d454c2fe6..d1764ea482 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -172,12 +172,12 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans // This has become noisy with implicit classes. if (settings.warnPolyImplicitOverload && settings.developer) { - clazz.info.decls filter (x => x.isImplicit && x.typeParams.nonEmpty) foreach { sym => + clazz.info.decls.foreach(sym => if (sym.isImplicit && sym.typeParams.nonEmpty) { // implicit classes leave both a module symbol and a method symbol as residue val alts = clazz.info.decl(sym.name).alternatives filterNot (_.isModule) if (alts.size > 1) alts foreach (x => reporter.warning(x.pos, "parameterized overloaded implicit methods are not visible as view bounds")) - } + }) } } @@ -1659,24 +1659,32 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans // inside annotations. applyRefchecksToAnnotations(tree) var result: Tree = tree match { - case DefDef(_, _, _, _, _, EmptyTree) if sym hasAnnotation NativeAttr => - sym resetFlag DEFERRED - transform(deriveDefDef(tree)(_ => typed(gen.mkSysErrorCall("native method stub")))) - - case ValDef(_, _, _, _) | DefDef(_, _, _, _, _, _) => + case vod: ValOrDefDef => checkDeprecatedOvers(tree) - checkInfiniteLoop(tree.asInstanceOf[ValOrDefDef]) + checkInfiniteLoop(vod) if (settings.warnNullaryUnit) checkNullaryMethodReturnType(sym) if (settings.warnInaccessible) { if (!sym.isConstructor && !sym.isEffectivelyFinalOrNotOverridden && !sym.isSynthetic) checkAccessibilityOfReferencedTypes(tree) } - tree match { - case dd: DefDef => checkByNameRightAssociativeDef(dd) - case _ => + vod match { + case dd: DefDef => + checkByNameRightAssociativeDef(dd) + + if (sym hasAnnotation NativeAttr) { + if (sym.owner.isTrait) { + reporter.error(tree.pos, "A trait cannot define a native method.") + tree + } else if (dd.rhs == EmptyTree) { + // pretend it had a stub implementation + sym resetFlag DEFERRED + deriveDefDef(dd)(_ => typed(gen.mkSysErrorCall("native method stub"))) + } else tree + } else tree + + case _ => tree } - tree case Template(parents, self, body) => localTyper = localTyper.atOwner(tree, currentOwner) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index dcf14612c9..9fa3564b2b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2068,35 +2068,39 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => (call, Nil) } - val (superConstr, superArgs) = decompose(rhs) - assert(superConstr.symbol ne null, superConstr)//debug - def superClazz = superConstr.symbol.owner - def superParamAccessors = superClazz.constrParamAccessors // associate superclass paramaccessors with their aliases - if (superConstr.symbol.isPrimaryConstructor && !superClazz.isJavaDefined && sameLength(superParamAccessors, superArgs)) { - for ((superAcc, superArg @ Ident(name)) <- superParamAccessors zip superArgs) { - if (mexists(vparamss)(_.symbol == superArg.symbol)) { - val alias = ( - superAcc.initialize.alias - orElse (superAcc getterIn superAcc.owner) - filter (alias => superClazz.info.nonPrivateMember(alias.name) == alias) - ) - if (alias.exists && !alias.accessed.isVariable && !isRepeatedParamType(alias.accessed.info)) { - val ownAcc = clazz.info decl name suchThat (_.isParamAccessor) match { - case acc if !acc.isDeferred && acc.hasAccessorFlag => acc.accessed - case acc => acc - } - ownAcc match { - case acc: TermSymbol if !acc.isVariable && !isByNameParamType(acc.info) => - debuglog(s"$acc has alias ${alias.fullLocationString}") - acc setAlias alias - case _ => + val (superConstr, superArgs) = decompose(rhs) + if (superConstr.symbol.isPrimaryConstructor) { + val superClazz = superConstr.symbol.owner + if (!superClazz.isJavaDefined) { + val superParamAccessors = superClazz.constrParamAccessors + if (sameLength(superParamAccessors, superArgs)) { + for ((superAcc, superArg@Ident(name)) <- superParamAccessors zip superArgs) { + if (mexists(vparamss)(_.symbol == superArg.symbol)) { + val alias = ( + superAcc.initialize.alias + orElse (superAcc getterIn superAcc.owner) + filter (alias => superClazz.info.nonPrivateMember(alias.name) == alias) + ) + if (alias.exists && !alias.accessed.isVariable && !isRepeatedParamType(alias.accessed.info)) { + val ownAcc = clazz.info decl name suchThat (_.isParamAccessor) match { + case acc if !acc.isDeferred && acc.hasAccessorFlag => acc.accessed + case acc => acc + } + ownAcc match { + case acc: TermSymbol if !acc.isVariable && !isByNameParamType(acc.info) => + debuglog(s"$acc has alias ${alias.fullLocationString}") + acc setAlias alias + case _ => + } + } } } } } } + pending.foreach(ErrorUtils.issueTypeError) } @@ -2391,19 +2395,20 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } // The block is an anonymous class definitions/instantiation pair // -> members that are hidden by the type of the block are made private - val toHide = ( - classDecls filter (member => - member.isTerm - && member.isPossibleInRefinement - && member.isPublic - && !matchesVisibleMember(member) - ) map (member => member - resetFlag (PROTECTED | LOCAL) - setFlag (PRIVATE | SYNTHETIC_PRIVATE) - setPrivateWithin NoSymbol - ) - ) - syntheticPrivates ++= toHide + classDecls foreach { toHide => + if (toHide.isTerm + && toHide.isPossibleInRefinement + && toHide.isPublic + && !matchesVisibleMember(toHide)) { + (toHide + resetFlag (PROTECTED | LOCAL) + setFlag (PRIVATE | SYNTHETIC_PRIVATE) + setPrivateWithin NoSymbol) + + syntheticPrivates += toHide + } + } + case _ => } } @@ -3332,7 +3337,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // #2064 duplErrorTree(WrongNumberOfArgsError(tree, fun)) } else if (lencmp > 0) { - tryTupleApply orElse duplErrorTree(TooManyArgsNamesDefaultsError(tree, fun)) + tryTupleApply orElse duplErrorTree { + val (namelessArgs, argPos) = removeNames(Typer.this)(args, params) + TooManyArgsNamesDefaultsError(tree, fun, formals, args, namelessArgs, argPos) + } } else if (lencmp == 0) { // we don't need defaults. names were used, so this application is transformed // into a block (@see transformNamedApplication in NamesDefaults) @@ -3396,7 +3404,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val lencmp2 = compareLengths(allArgs, formals) if (!sameLength(allArgs, args) && callToCompanionConstr(context, funSym)) { - duplErrorTree(ModuleUsingCompanionClassDefaultArgsErrror(tree)) + duplErrorTree(ModuleUsingCompanionClassDefaultArgsError(tree)) } else if (lencmp2 > 0) { removeNames(Typer.this)(allArgs, params) // #3818 duplErrTree @@ -3641,10 +3649,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper reportAnnotationError(MultipleArgumentListForAnnotationError(ann)) } else { - val annScope = annType.decls - .filter(sym => sym.isMethod && !sym.isConstructor && sym.isJavaDefined) + val annScopeJava = + if (isJava) annType.decls.filter(sym => sym.isMethod && !sym.isConstructor && sym.isJavaDefined) + else EmptyScope // annScopeJava is only used if isJava + val names = mutable.Set[Symbol]() - names ++= (if (isJava) annScope.iterator + names ++= (if (isJava) annScopeJava.iterator else typedFun.tpe.params.iterator) def hasValue = names exists (_.name == nme.value) @@ -3655,7 +3665,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val nvPairs = args map { case arg @ AssignOrNamedArg(Ident(name), rhs) => - val sym = if (isJava) annScope.lookup(name) + val sym = if (isJava) annScopeJava.lookup(name) else findSymbol(typedFun.tpe.params)(_.name == name) if (sym == NoSymbol) { reportAnnotationError(UnknownAnnotationNameError(arg, name)) diff --git a/src/library/scala/collection/immutable/HashMap.scala b/src/library/scala/collection/immutable/HashMap.scala index eac9c14f3f..6b29b084aa 100644 --- a/src/library/scala/collection/immutable/HashMap.scala +++ b/src/library/scala/collection/immutable/HashMap.scala @@ -201,7 +201,7 @@ object HashMap extends ImmutableMapFactory[HashMap] with BitOperations.Int { if (this.value.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]) this else new HashMap1(key, hash, value, kv) } else { - val nkv = merger(this.kv, kv) + val nkv = merger(this.ensurePair, if(kv != null) kv else (key, value)) new HashMap1(nkv._1, hash, nkv._2, nkv) } } else { diff --git a/src/library/scala/collection/immutable/Range.scala b/src/library/scala/collection/immutable/Range.scala index 0c24d17c15..2e56750115 100644 --- a/src/library/scala/collection/immutable/Range.scala +++ b/src/library/scala/collection/immutable/Range.scala @@ -81,8 +81,7 @@ extends scala.collection.AbstractSeq[Int] || (start == end && !isInclusive) ) - @deprecated("this method will be made private, use `length` instead", "2.11.0") - final val numRangeElements: Int = { + private val numRangeElements: Int = { if (step == 0) throw new IllegalArgumentException("step cannot be 0.") else if (isEmpty) 0 else { @@ -92,21 +91,16 @@ extends scala.collection.AbstractSeq[Int] } } - @deprecated("this method will be made private, use `last` instead", "2.11.0") - final val lastElement = - if (isEmpty) start - step - else step match { - case 1 => if (isInclusive) end else end-1 - case -1 => if (isInclusive) end else end+1 - case _ => - val remainder = (gap % step).toInt - if (remainder != 0) end - remainder - else if (isInclusive) end - else end - step - } - - @deprecated("this method will be made private", "2.11.0") - final val terminalElement = lastElement + step + // This field has a sensible value only for non-empty ranges + private val lastElement = step match { + case 1 => if (isInclusive) end else end-1 + case -1 => if (isInclusive) end else end+1 + case _ => + val remainder = (gap % step).toInt + if (remainder != 0) end - remainder + else if (isInclusive) end + else end - step + } /** The last element of this range. This method will return the correct value * even if there are too many elements to iterate over. diff --git a/src/library/scala/collection/immutable/StringLike.scala b/src/library/scala/collection/immutable/StringLike.scala index b468b09a9d..155d25d933 100644 --- a/src/library/scala/collection/immutable/StringLike.scala +++ b/src/library/scala/collection/immutable/StringLike.scala @@ -286,31 +286,39 @@ self => def r(groupNames: String*): Regex = new Regex(toString, groupNames: _*) /** - * @throws java.lang.IllegalArgumentException - If the string does not contain a parsable boolean. + * @throws java.lang.IllegalArgumentException If the string does not contain a parsable `Boolean`. */ def toBoolean: Boolean = parseBoolean(toString) /** - * @throws java.lang.NumberFormatException - If the string does not contain a parsable byte. + * Parse as a `Byte` (string must contain only decimal digits and optional leading `-`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Byte`. */ def toByte: Byte = java.lang.Byte.parseByte(toString) /** - * @throws java.lang.NumberFormatException - If the string does not contain a parsable short. + * Parse as a `Short` (string must contain only decimal digits and optional leading `-`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Short`. */ def toShort: Short = java.lang.Short.parseShort(toString) /** - * @throws java.lang.NumberFormatException - If the string does not contain a parsable int. + * Parse as an `Int` (string must contain only decimal digits and optional leading `-`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Int`. */ def toInt: Int = java.lang.Integer.parseInt(toString) /** - * @throws java.lang.NumberFormatException - If the string does not contain a parsable long. + * Parse as a `Long` (string must contain only decimal digits and optional leading `-`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Long`. */ def toLong: Long = java.lang.Long.parseLong(toString) /** - * @throws java.lang.NumberFormatException - If the string does not contain a parsable float. + * Parse as a `Float` (surrounding whitespace is removed with a `trim`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Float`. + * @throws java.lang.NullPointerException If the string is null. */ def toFloat: Float = java.lang.Float.parseFloat(toString) /** - * @throws java.lang.NumberFormatException - If the string does not contain a parsable double. + * Parse as a `Double` (surrounding whitespace is removed with a `trim`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Double`. + * @throws java.lang.NullPointerException If the string is null. */ def toDouble: Double = java.lang.Double.parseDouble(toString) diff --git a/src/library/scala/collection/mutable/OpenHashMap.scala b/src/library/scala/collection/mutable/OpenHashMap.scala index 5bea1634c4..ca08f475ce 100644 --- a/src/library/scala/collection/mutable/OpenHashMap.scala +++ b/src/library/scala/collection/mutable/OpenHashMap.scala @@ -115,9 +115,8 @@ extends AbstractMap[Key, Value] * @param hash hash value for `key` */ private[this] def findIndex(key: Key, hash: Int): Int = { - var j = hash var index = hash & mask - var perturb = index + var j = 0 /** Index of the first slot containing a deleted entry, or -1 if none found yet. */ var firstDeletedIndex = -1 @@ -130,9 +129,8 @@ extends AbstractMap[Key, Value] if (firstDeletedIndex == -1 && entry.value == None) firstDeletedIndex = index - j = 5 * j + 1 + perturb - perturb >>= 5 - index = j & mask + j += 1 + index = (index + j) & mask entry = table(index) } @@ -197,20 +195,17 @@ extends AbstractMap[Key, Value] def get(key : Key) : Option[Value] = { val hash = hashOf(key) - - var j = hash var index = hash & mask - var perturb = index var entry = table(index) + var j = 0 while(entry != null){ if (entry.hash == hash && entry.key == key){ return entry.value } - j = 5 * j + 1 + perturb - perturb >>= 5 - index = j & mask + j += 1 + index = (index + j) & mask entry = table(index) } None diff --git a/src/library/scala/native.scala b/src/library/scala/native.scala index dbacc78618..49d3ced805 100644 --- a/src/library/scala/native.scala +++ b/src/library/scala/native.scala @@ -16,8 +16,11 @@ package scala * @native def f(x: Int, y: List[Long]): String = ... * }}} * - * Method body is not generated if method is marked with `@native`, - * but it is type checked when present. + * A `@native` method is compiled to the platform's native method, + * while discarding the method's body (if any). The body will be type checked if present. * - * @since 2.6 */ + * A method marked @native must be a member of a class, not a trait (since 2.12). + * + * @since 2.6 + */ class native extends scala.annotation.StaticAnnotation {} diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index ca6c893d13..fe6d88e7c7 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -863,12 +863,13 @@ trait Definitions extends api.StandardDefinitions { // Scopes() // must filter out "universal" members (getClass is deferred for some reason) val deferredMembers = ( - tp membersBasedOnFlags (excludedFlags = BridgeAndPrivateFlags, requiredFlags = METHOD) - filter (mem => mem.isDeferredNotJavaDefault && !isUniversalMember(mem)) // TODO: test + tp.membersBasedOnFlags(excludedFlags = BridgeAndPrivateFlags, requiredFlags = METHOD).toList.filter( + mem => mem.isDeferredNotJavaDefault && !isUniversalMember(mem) + ) // TODO: test ) // if there is only one, it's monomorphic and has a single argument list - if (deferredMembers.size == 1 && + if (deferredMembers.lengthCompare(1) == 0 && deferredMembers.head.typeParams.isEmpty && deferredMembers.head.info.paramSectionCount == 1) deferredMembers.head diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index c93ecac3fa..20a485469d 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -240,6 +240,7 @@ trait StdNames { final val Any: NameType = "Any" final val AnyVal: NameType = "AnyVal" + final val App: NameType = "App" final val FlagSet: NameType = "FlagSet" final val Mirror: NameType = "Mirror" final val Modifiers: NameType = "Modifiers" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 3b886d357f..ba195363c1 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2036,11 +2036,9 @@ trait Symbols extends api.Symbols { self: SymbolTable => } } } - private final def caseFieldAccessorsUnsorted: List[Symbol] = - (info.decls filter (_.isCaseAccessorMethod)).toList + private final def caseFieldAccessorsUnsorted: List[Symbol] = info.decls.toList.filter(_.isCaseAccessorMethod) - final def constrParamAccessors: List[Symbol] = - info.decls.filter(sym => !sym.isMethod && sym.isParamAccessor).toList + final def constrParamAccessors: List[Symbol] = info.decls.toList.filter(sym => !sym.isMethod && sym.isParamAccessor) /** The symbol accessed by this accessor (getter or setter) function. */ final def accessed: Symbol = { diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 56d62f3efc..66a5f08e96 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -1,5 +1,5 @@ /* NSC -- new Scala compiler - * Copyright 2005-2015 LAMP/EPFL + * Copyright 2005-2016 LAMP/EPFL * @author Alexander Spoon */ package scala @@ -15,7 +15,7 @@ import scala.tools.asm.ClassReader import scala.util.Properties.jdkHome import scala.tools.nsc.util.{ ClassPath, stringFromStream } import scala.reflect.classTag -import scala.reflect.internal.util.{ BatchSourceFile, ScalaClassLoader } +import scala.reflect.internal.util.{ BatchSourceFile, ScalaClassLoader, NoPosition } import ScalaClassLoader._ import scala.reflect.io.File import scala.tools.util._ @@ -174,10 +174,19 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) echo("\n" + msg) in.redrawLine() } - protected def echo(msg: String) = { + protected var mum = false + protected def echo(msg: String) = if (!mum) { out println msg out.flush() } + // turn off intp reporter and our echo + def mumly[A](op: => A): A = + if (isReplDebug) op + else intp beQuietDuring { + val saved = mum + mum = true + try op finally mum = saved + } /** Search the history */ def searchHistory(_cmdline: String) { @@ -406,12 +415,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) * command() for each line of input, and stops when * command() returns false. */ - @tailrec final def loop(): LineResult = { + final def loop(): LineResult = loop(readOneLine()) + + @tailrec final def loop(line: String): LineResult = { import LineResults._ - readOneLine() match { - case null => EOF - case line => if (try processLine(line) catch crashRecovery) loop() else ERR - } + if (line == null) EOF + else if (try processLine(line) catch crashRecovery) loop(readOneLine()) + else ERR } /** interpret all lines from a specified file */ @@ -480,11 +490,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) def editCommand(what: String): Result = editCommand(what, Properties.envOrNone("EDITOR")) def editCommand(what: String, editor: Option[String]): Result = { - def diagnose(code: String) = { - echo("The edited code is incomplete!\n") - val errless = intp compileSources new BatchSourceFile("<pastie>", s"object pastel {\n$code\n}") - if (errless) echo("The compiler reports no errors.") - } + def diagnose(code: String): Unit = paste.incomplete("The edited code is incomplete!\n", "<edited>", code) def edit(text: String): Result = editor match { case Some(ed) => @@ -563,9 +569,9 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } } - def withFile[A](filename: String)(action: File => A): Option[A] = { + def withFile[A](filename: String)(action: File => A): Option[A] = intp.withLabel(filename) { val res = Some(File(filename)) filter (_.exists) map action - if (res.isEmpty) echo("That file does not exist") // courtesy side-effect + if (res.isEmpty) intp.reporter.warning(NoPosition, s"File `$filename' does not exist.") // courtesy side-effect res } @@ -702,6 +708,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) */ def pasteCommand(arg: String): Result = { var shouldReplay: Option[String] = None + var label = "<pastie>" def result = Result(keepRunning = true, shouldReplay) val (raw, file, margin) = if (arg.isEmpty) (false, None, None) @@ -722,6 +729,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } val code = (file, margin) match { case (Some(name), None) => + label = name withFile(name) { f => shouldReplay = Some(s":paste $arg") val s = f.slurp.trim @@ -744,21 +752,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) text } def interpretCode() = { - val res = intp interpret code - // if input is incomplete, let the compiler try to say why - if (res == IR.Incomplete) { - echo("The pasted code is incomplete!\n") - // Remembrance of Things Pasted in an object - val errless = intp compileSources new BatchSourceFile("<pastie>", s"object pastel {\n$code\n}") - if (errless) echo("...but compilation found no error? Good luck with that.") - } - } - def compileCode() = { - val errless = intp compileSources new BatchSourceFile("<pastie>", code) - if (!errless) echo("There were compilation errors!") + if (intp.withLabel(label)(intp interpret code) == IR.Incomplete) + paste.incomplete("The pasted code is incomplete!\n", label, code) } + def compileCode() = paste.compilePaste(label = label, code = code) + if (code.nonEmpty) { - if (raw) compileCode() else interpretCode() + if (raw || paste.isPackaged(code)) compileCode() else interpretCode() } result } @@ -766,6 +766,27 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) private object paste extends Pasted(prompt) { def interpret(line: String) = intp interpret line def echo(message: String) = ILoop.this echo message + + val leadingElement = raw"(?s)\s*(package\s|/)".r + def isPackaged(code: String): Boolean = { + leadingElement.findPrefixMatchOf(code) + .map(m => if (m.group(1) == "/") intp.parse.packaged(code) else true) + .getOrElse(false) + } + + // if input is incomplete, wrap and compile for diagnostics. + def incomplete(message: String, label: String, code: String): Boolean = { + echo(message) + val errless = intp.compileSources(new BatchSourceFile(label, s"object pastel {\n$code\n}")) + if (errless) echo("No error found in incomplete source.") + errless + } + + def compilePaste(label: String, code: String): Boolean = { + val errless = intp.compileSources(new BatchSourceFile(label, code)) + if (!errless) echo("There were compilation errors!") + errless + } } private object invocation { @@ -826,19 +847,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } } - // runs :load `file` on any files passed via -i - def loadFiles(settings: Settings) = settings match { - case settings: GenericRunnerSettings => - for (filename <- settings.loadfiles.value) { - val cmd = ":load " + filename - command(cmd) - addReplay(cmd) - echo("") - } - case _ => - } - - /** Tries to create a JLineReader, falling back to SimpleReader, + /** Tries to create a jline.InteractiveReader, falling back to SimpleReader, * unless settings or properties are such that it should start with SimpleReader. * The constructor of the InteractiveReader must take a Completion strategy, * supplied as a `() => Completion`; the Completion object provides a concrete Completer. @@ -877,49 +886,115 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } } - private def loopPostInit() { - // Bind intp somewhere out of the regular namespace where - // we can get at it in generated code. - intp.quietBind(NamedParam[IMain]("$intp", intp)(tagOfIMain, classTag[IMain])) - // Auto-run code via some setting. - ( replProps.replAutorunCode.option - flatMap (f => io.File(f).safeSlurp()) - foreach (intp quietRun _) - ) - // classloader and power mode setup - intp.setContextClassLoader() - if (isReplPower) { - replProps.power setValue true - unleashAndSetPhase() - asyncMessage(power.banner) - } - // SI-7418 Now, and only now, can we enable TAB completion. - in.postInit() - } - - // start an interpreter with the given settings + /** Start an interpreter with the given settings. + * @return true if successful + */ def process(settings: Settings): Boolean = savingContextLoader { - this.settings = settings - createInterpreter() + def newReader = in0.fold(chooseReader(settings))(r => SimpleReader(r, out, interactive = true)) - // sets in to some kind of reader depending on environmental cues - in = in0.fold(chooseReader(settings))(r => SimpleReader(r, out, interactive = true)) - globalFuture = Future { - intp.initializeSynchronous() - loopPostInit() - !intp.reporter.hasErrors + /** Reader to use before interpreter is online. */ + def preLoop = { + val sr = SplashReader(newReader) { r => + in = r + in.postInit() + } + in = sr + SplashLoop(sr, prompt) } - loadFiles(settings) - printWelcome() - try loop() match { - case LineResults.EOF => out print Properties.shellInterruptedString - case _ => + /* Actions to cram in parallel while collecting first user input at prompt. + * Run with output muted both from ILoop and from the intp reporter. + */ + def loopPostInit(): Unit = mumly { + // Bind intp somewhere out of the regular namespace where + // we can get at it in generated code. + intp.quietBind(NamedParam[IMain]("$intp", intp)(tagOfIMain, classTag[IMain])) + + // Auto-run code via some setting. + ( replProps.replAutorunCode.option + flatMap (f => File(f).safeSlurp()) + foreach (intp quietRun _) + ) + // power mode setup + if (isReplPower) { + replProps.power setValue true + unleashAndSetPhase() + asyncMessage(power.banner) + } + loadInitFiles() + // SI-7418 Now, and only now, can we enable TAB completion. + in.postInit() + } + def loadInitFiles(): Unit = settings match { + case settings: GenericRunnerSettings => + for (f <- settings.loadfiles.value) { + loadCommand(f) + addReplay(s":load $f") + } + for (f <- settings.pastefiles.value) { + pasteCommand(f) + addReplay(s":paste $f") + } + case _ => + } + // wait until after startup to enable noisy settings + def withSuppressedSettings[A](body: => A): A = { + val ss = this.settings + import ss._ + val noisy = List(Xprint, Ytyperdebug) + val noisesome = noisy.exists(!_.isDefault) + val current = (Xprint.value, Ytyperdebug.value) + if (isReplDebug || !noisesome) body + else { + this.settings.Xprint.value = List.empty + this.settings.Ytyperdebug.value = false + try body + finally { + Xprint.value = current._1 + Ytyperdebug.value = current._2 + intp.global.printTypings = current._2 + } + } } - catch AbstractOrMissingHandler() - finally closeInterpreter() + def startup(): String = withSuppressedSettings { + // starting + printWelcome() + + // let them start typing + val splash = preLoop + splash.start() - true + // while we go fire up the REPL + try { + createInterpreter() + intp.initializeSynchronous() + globalFuture = Future successful true + if (intp.reporter.hasErrors) { + echo("Interpreter encountered errors during initialization!") + null + } else { + loopPostInit() + val line = splash.line // what they typed in while they were waiting + if (line == null) { // they ^D + try out print Properties.shellInterruptedString + finally closeInterpreter() + } + line + } + } finally splash.stop() + } + this.settings = settings + startup() match { + case null => false + case line => + try loop(line) match { + case LineResults.EOF => out print Properties.shellInterruptedString + case _ => + } + catch AbstractOrMissingHandler() + finally closeInterpreter() + true + } } @deprecated("Use `process` instead", "2.9.0") diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 763a8ccd1b..44784aa953 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -1,5 +1,5 @@ /* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL + * Copyright 2005-2016 LAMP/EPFL * @author Martin Odersky */ @@ -69,13 +69,14 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends lazy val isClassBased: Boolean = settings.Yreplclassbased.value - private[nsc] var printResults = true // whether to print result lines - private[nsc] var totalSilence = false // whether to print anything - private var _initializeComplete = false // compiler is initialized - private var _isInitialized: Future[Boolean] = null // set up initialization future - private var bindExceptions = true // whether to bind the lastException variable - private var _executionWrapper = "" // code to be wrapped around all lines - var partialInput: String = "" // code accumulated in multi-line REPL input + private[nsc] var printResults = true // whether to print result lines + private[nsc] var totalSilence = false // whether to print anything + private var _initializeComplete = false // compiler is initialized + private var _isInitialized: Future[Boolean] = null // set up initialization future + private var bindExceptions = true // whether to bind the lastException variable + private var _executionWrapper = "" // code to be wrapped around all lines + var partialInput: String = "" // code accumulated in multi-line REPL input + private var label = "<console>" // compilation unit name for reporting /** We're going to go to some trouble to initialize the compiler asynchronously. * It's critical that nothing call into it until it's been initialized or we will @@ -103,6 +104,12 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends try body finally if (!saved) settings.nowarn.value = false } + // Apply a temporary label for compilation (for example, script name) + def withLabel[A](temp: String)(body: => A): A = { + val saved = label + label = temp + try body finally label = saved + } // the expanded prompt but without color escapes and without leading newline, for purposes of indenting lazy val formatting = Formatting.forPrompt(replProps.promptText) @@ -749,7 +756,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends case Right(result) => Right(result) } - def compile(source: String): Boolean = compileAndSaveRun("<console>", source) + def compile(source: String): Boolean = compileAndSaveRun(label, source) /** The innermost object inside the wrapper, found by * following accessPath into the outer one. @@ -1094,17 +1101,27 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends case class Incomplete(trees: List[Tree]) extends Result case class Success(trees: List[Tree]) extends Result - def apply(line: String): Result = debugging(s"""parse("$line")""") { + def apply(line: String): Result = debugging(s"""parse("$line")""") { var isIncomplete = false def parse = { reporter.reset() - val trees = newUnitParser(line).parseStats() + val trees = newUnitParser(line, label).parseStats() if (reporter.hasErrors) Error(trees) else if (isIncomplete) Incomplete(trees) else Success(trees) } - currentRun.parsing.withIncompleteHandler((_, _) => isIncomplete = true) {parse} - + currentRun.parsing.withIncompleteHandler((_, _) => isIncomplete = true)(parse) + } + // code has a named package + def packaged(line: String): Boolean = { + def parses = { + reporter.reset() + val tree = newUnitParser(line).parse() + !reporter.hasErrors && { + tree match { case PackageDef(Ident(id), _) => id != nme.EMPTY_PACKAGE_NAME case _ => false } + } + } + beSilentDuring(parses) } } diff --git a/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala index 71753a3e39..1f81d9965c 100644 --- a/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala +++ b/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala @@ -50,3 +50,98 @@ object InteractiveReader { def createDefault(): InteractiveReader = apply() // used by sbt } +/** Collect one line of user input from the supplied reader. + * Runs on a new thread while the REPL is initializing on the main thread. + * + * The user can enter text or a `:paste` command. + */ +class SplashLoop(reader: InteractiveReader, prompt: String) extends Runnable { + import java.util.concurrent.SynchronousQueue + import scala.compat.Platform.EOL + + private val result = new SynchronousQueue[Option[String]] + @volatile private var running: Boolean = _ + private var thread: Thread = _ + + /** Read one line of input which can be retrieved with `line`. */ + def run(): Unit = { + var line = "" + try + do { + line = reader.readLine(prompt) + if (line != null) { + line = process(line.trim) + } + } while (line != null && line.isEmpty && running) + finally { + result.put(Option(line)) + } + } + + /** Check for `:paste` command. */ + private def process(line: String): String = { + def isPrefix(s: String, p: String, n: Int) = ( + //s != null && p.inits.takeWhile(_.length >= n).exists(s == _) + s != null && s.length >= n && s.length <= p.length && s == p.take(s.length) + ) + if (isPrefix(line, ":paste", 3)) { + // while collecting lines, check running flag + var help = f"// Entering paste mode (ctrl-D to finish)%n%n" + def readWhile(cond: String => Boolean) = { + Iterator continually reader.readLine(help) takeWhile { x => + help = "" + x != null && cond(x) + } + } + val text = (readWhile(_ => running) mkString EOL).trim + val next = + if (text.isEmpty) "// Nothing pasted, nothing gained." + else "// Exiting paste mode, now interpreting." + Console println f"%n${next}%n" + text + } else { + line + } + } + + def start(): Unit = result.synchronized { + require(thread == null, "Already started") + thread = new Thread(this) + running = true + thread.start() + } + + def stop(): Unit = result.synchronized { + running = false + if (thread != null) thread.interrupt() + thread = null + } + + /** Block for the result line, or null on ctl-D. */ + def line: String = result.take getOrElse null +} +object SplashLoop { + def apply(reader: SplashReader, prompt: String): SplashLoop = new SplashLoop(reader, prompt) +} + +/** Reader during splash. Handles splash-completion with a stub, otherwise delegates. */ +class SplashReader(reader: InteractiveReader, postIniter: InteractiveReader => Unit) extends InteractiveReader { + /** Invoke the postInit action with the underlying reader. */ + override def postInit(): Unit = postIniter(reader) + + override val interactive: Boolean = reader.interactive + + override def reset(): Unit = reader.reset() + override def history: History = reader.history + override val completion: Completion = NoCompletion + override def redrawLine(): Unit = reader.redrawLine() + + override protected[interpreter] def readOneLine(prompt: String): String = ??? // unused + override protected[interpreter] def readOneKey(prompt: String): Int = ??? // unused + + override def readLine(prompt: String): String = reader.readLine(prompt) +} +object SplashReader { + def apply(reader: InteractiveReader)(postIniter: InteractiveReader => Unit) = + new SplashReader(reader, postIniter) +} diff --git a/test/benchmarks/.gitignore b/test/benchmarks/.gitignore new file mode 100644 index 0000000000..ce4d893417 --- /dev/null +++ b/test/benchmarks/.gitignore @@ -0,0 +1,14 @@ +/project/project/ +/project/target/ +/target/ + +# what appears to be a Scala IDE-generated file +.cache-main + +# standard Eclipse output directory +/bin/ + +# sbteclipse-generated Eclipse files +/.classpath +/.project +/.settings/ diff --git a/test/benchmarks/README.md b/test/benchmarks/README.md new file mode 100644 index 0000000000..370d610bc4 --- /dev/null +++ b/test/benchmarks/README.md @@ -0,0 +1,105 @@ +# Scala library benchmarks + +This directory is a standalone SBT project, within the Scala project, +that makes use of the [SBT plugin](https://github.com/ktoso/sbt-jmh) for [JMH](http://openjdk.java.net/projects/code-tools/jmh/). + +## Running a benchmark + +The benchmarks require first building Scala into `../../build/pack` with `ant`. +If you want to build with `sbt dist/mkPack` instead, +you'll need to change `scalaHome` in this project. + +You'll then need to know the fully-qualified name of the benchmark runner class. +The benchmarking classes are organized under `src/main/scala`, +in the same package hierarchy as the classes that they test. +Assuming that we're benchmarking `scala.collection.mutable.OpenHashMap`, +the benchmark runner would likely be named `scala.collection.mutable.OpenHashMapRunner`. +Using this example, one would simply run + + jmh:runMain scala.collection.mutable.OpenHashMapRunner + +in SBT. +SBT should be run _from this directory_. + +The JMH results can be found under `target/jmh-results/`. +`target` gets deleted on an SBT `clean`, +so you should copy these files out of `target` if you wish to preserve them. + +## Creating a benchmark and runner + +The benchmarking classes use the same package hierarchy as the classes that they test +in order to make it easy to expose, in package scope, members of the class under test, +should that be necessary for benchmarking. + +There are two types of classes in the source directory: +those suffixed `Benchmark` and those suffixed `Runner`. +The former are benchmarks that can be run directly using `jmh:run`; +however, they are normally run from a corresponding class of the latter type, +which is run using `jmh:runMain` (as described above). +This …`Runner` class is useful for setting appropriate JMH command options, +and for processing the JMH results into files that can be read by other tools, such as Gnuplot. + +The `benchmark.JmhRunner` trait should be woven into any runner class, for the standard behavior that it provides. +This includes creating output files in a subdirectory of `target/jmh-results` +derived from the fully-qualified package name of the `Runner` class. + +## Some useful HotSpot options +Adding these to the `jmh:run` or `jmh:runMain` command line may help if you're using the HotSpot (Oracle, OpenJDK) compiler. +They require prefixing with `-jvmArgs`. +See [the Java documentation](http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html) for more options. + +### Viewing JIT compilation events +Adding `-XX:+PrintCompilation` shows when Java methods are being compiled or deoptimized. +At the most basic level, +these messages will tell you whether the code that you're measuring is still being tuned, +so that you know whether you're running enough warm-up iterations. +See [Kris Mok's notes](https://gist.github.com/rednaxelafx/1165804#file-notes-md) to interpret the output in detail. + +### Consider GC events +If you're not explicitly performing `System.gc()` calls outside of your benchmarking code, +you should add the JVM option `-verbose:gc` to understand the effect that GCs may be having on your tests. + +### "Diagnostic" options +These require the `-XX:+UnlockDiagnosticVMOptions` JVM option. + +#### Viewing inlining events +Add `-XX:+PrintInlining`. + +#### Viewing the disassembled code +If you're running OpenJDK or Oracle JVM, +you may need to install the disassembler library (`hsdis-amd64.so` for the `amd64` architecture). +In Debian, this is available in +<a href="https://packages.debian.org/search?keywords=libhsdis0-fcml">the `libhsdis0-fcml` package</a>. +For an Oracle (or other compatible) JVM not set up by your distribution, +you may also need to copy or link the disassembler library +to the `jre/lib/`_`architecture`_ directory inside your JVM installation directory. + +To show the assembly code corresponding to the code generated by the JIT compiler for specific methods, +add `-XX:CompileCommand=print,scala.collection.mutable.OpenHashMap::*`, +for example, to show all of the methods in the `scala.collection.mutable.OpenHashMap` class. + +To show it for _all_ methods, add `-XX:+PrintAssembly`. +(This is usually excessive.) + +## Useful reading +* [OpenJDK advice on microbenchmarks](https://wiki.openjdk.java.net/display/HotSpot/MicroBenchmarks) +* Brian Goetz's "Java theory and practice" articles: + * "[Dynamic compilation and performance measurement](http://www.ibm.com/developerworks/java/library/j-jtp12214/)" + * "[Anatomy of a flawed benchmark](http://www.ibm.com/developerworks/java/library/j-jtp02225/)" +* [Doug Lea's JSR 166 benchmarks](http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/test/loops/) +* "[Measuring performance](http://docs.scala-lang.org/overviews/parallel-collections/performance.html)" of Scala parallel collections + +## Legacy frameworks + +An older version of the benchmarking framework is still present in this directory, in the following locations: + +<dl> +<dt><code>bench</code></dt> +<dd>A script to run the old benchmarks.</dd> +<dt><code>source.list</code></dt> +<dd>A temporary file used by <code>bench</code>.</dd> +<dt><code>src/scala/</code></dt> +<dd>The older benchmarks, including the previous framework.</dd> +</dl> + +Another, older set of benchmarks is present in `../benchmarking/`. diff --git a/test/benchmarks/build.sbt b/test/benchmarks/build.sbt new file mode 100644 index 0000000000..31cee701ad --- /dev/null +++ b/test/benchmarks/build.sbt @@ -0,0 +1,11 @@ +scalaHome := Some(file("../../build/pack")) +scalaVersion := "2.12.0-dev" +scalacOptions ++= Seq("-feature", "-Yopt:l:classpath") + +lazy val root = (project in file(".")). + enablePlugins(JmhPlugin). + settings( + name := "test-benchmarks", + version := "0.0.1", + libraryDependencies += "org.openjdk.jol" % "jol-core" % "0.4" + ) diff --git a/test/benchmarks/project/plugins.sbt b/test/benchmarks/project/plugins.sbt new file mode 100644 index 0000000000..e11aa29f3b --- /dev/null +++ b/test/benchmarks/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.6") diff --git a/test/benchmarks/src/main/scala/benchmark/JmhRunner.scala b/test/benchmarks/src/main/scala/benchmark/JmhRunner.scala new file mode 100644 index 0000000000..cc75be529d --- /dev/null +++ b/test/benchmarks/src/main/scala/benchmark/JmhRunner.scala @@ -0,0 +1,16 @@ +package benchmark + +import java.io.File + +/** Common code for JMH runner objects. */ +trait JmhRunner { + private[this] val parentDirectory = new File("target", "jmh-results") + + /** Return the output directory for this class, creating the directory if necessary. */ + protected def outputDirectory: File = { + val subdir = getClass.getPackage.getName.replace('.', File.separatorChar) + val dir = new File(parentDirectory, subdir) + if (!dir.isDirectory) dir.mkdirs() + dir + } +} diff --git a/test/benchmarks/src/main/scala/benchmark/KeySeq.scala b/test/benchmarks/src/main/scala/benchmark/KeySeq.scala new file mode 100644 index 0000000000..126b92b3b6 --- /dev/null +++ b/test/benchmarks/src/main/scala/benchmark/KeySeq.scala @@ -0,0 +1,24 @@ +package benchmark + +/** A sequence of keys. + * + * Tests of maps and sets require a sequence of keys that can be used + * to add entries and possibly to find them again. + * This type provides such a sequence. + * + * Note that this needn't be a "sequence" in the full sense of [[collection.Seq]], + * particularly in that it needn't extend [[PartialFunction]]. + * + * @tparam K the type of the keys + */ +trait KeySeq[K] { + /** Selects a key by its index in the sequence. + * Repeated calls with the same index return the same key (by reference equality). + * + * @param idx The index to select. Should be non-negative and less than `size`. + */ + def apply(idx: Int): K + + /** The size of this sequence. */ + def size: Int +} diff --git a/test/benchmarks/src/main/scala/benchmark/KeySeqBuilder.scala b/test/benchmarks/src/main/scala/benchmark/KeySeqBuilder.scala new file mode 100644 index 0000000000..95f6c7afd7 --- /dev/null +++ b/test/benchmarks/src/main/scala/benchmark/KeySeqBuilder.scala @@ -0,0 +1,33 @@ +package benchmark + +/** Builder of a [[KeySeq]] + * + * @tparam K the type of the keys + */ +trait KeySeqBuilder[K] { + /** Return a [[KeySeq]] having at least the given size. */ + def build(size: Int): KeySeq[K] +} + +object KeySeqBuilder { + /** Builder of a sequence of `Int` keys. + * Simply maps the sequence index to itself. + */ + implicit object IntKeySeqBuilder extends KeySeqBuilder[Int] { + def build(_size: Int) = new KeySeq[Int] { + def apply(idx: Int) = idx + def size = _size + } + } + + /** Builder of a sequence of `AnyRef` keys. */ + implicit object AnyRefKeySeqBuilder extends KeySeqBuilder[AnyRef] { + def build(_size: Int) = new KeySeq[AnyRef] { + private[this] val arr = new Array[AnyRef](size) + for (i <- 0 until size) arr(i) = new AnyRef() + + def apply(idx: Int) = arr(idx) + def size = _size + } + } +} diff --git a/test/benchmarks/src/main/scala/scala/collection/mutable/OpenHashMapBenchmark.scala b/test/benchmarks/src/main/scala/scala/collection/mutable/OpenHashMapBenchmark.scala new file mode 100644 index 0000000000..64e2244499 --- /dev/null +++ b/test/benchmarks/src/main/scala/scala/collection/mutable/OpenHashMapBenchmark.scala @@ -0,0 +1,308 @@ +package scala.collection.mutable; + +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra._ +import org.openjdk.jmh.runner.IterationType +import org.openjdk.jol.info.GraphLayout + +import benchmark._ +import java.util.concurrent.TimeUnit + +/** Utilities for the [[OpenHashMapBenchmark]]. + * + * The method calls are tested by looping to the size desired for the map; + * instead of using the JMH harness, which iterates for a fixed length of time. + */ +private object OpenHashMapBenchmark { + + /** Abstract state container for the `put()` bulk calling tests. + * + * Provides an array of adequately-sized, empty maps to each invocation, + * so that hash table allocation won't be done during measurement. + * Provides enough maps to make each invocation long enough to avoid timing artifacts. + * Performs a GC after re-creating the empty maps before every invocation, + * so that only the GCs caused by the invocation contribute to the measurement. + * + * Records the memory used by all the maps in the last invocation of each iteration. + * + * @tparam K type of the map keys to be used in the test + */ + @State(Scope.Thread) + private[this] abstract class BulkPutState[K](implicit keyBuilder: KeySeqBuilder[K]) { + /** A lower-bound estimate of the number of nanoseconds per `put()` call */ + private[this] val nanosPerPut: Double = 5 + + /** Minimum number of nanoseconds per invocation, so as to avoid timing artifacts. */ + private[this] val minNanosPerInvocation = 1000000 // one millisecond + + /** Size of the maps created in this trial. */ + private[this] var size: Int = _ + + /** Total number of entries in all of the `maps` combined. */ + private[this] var _mapEntries: Int = _ + protected def mapEntries = _mapEntries + + /** Number of operations performed in the current invocation. */ + private[this] var _operations: Int = _ + protected def operations = _operations + + /** Bytes of memory used in the object graphs of all the maps. */ + private[this] var _memory: Long = _ + protected def memory = _memory + + /** The sequence of keys to store into a map. */ + private[this] var _keys: KeySeq[K] = _ + def keys() = _keys + + var maps: Array[OpenHashMap[K,Int]] = null + + @Setup + def threadSetup(params: BenchmarkParams) { + size = params.getParam("size").toInt + val n = math.ceil(minNanosPerInvocation / (nanosPerPut * size)).toInt + _mapEntries = size * n + _keys = keyBuilder.build(size) + maps = new Array(n) + } + + @Setup(Level.Iteration) + def iterationSetup { + _operations = 0 + } + + @Setup(Level.Invocation) + def setup(params: IterationParams) { + for (i <- 0 until maps.length) maps(i) = new OpenHashMap[K,Int](size) + + if (params.getType == IterationType.MEASUREMENT) { + _operations += _mapEntries + System.gc() // clean up after last invocation + } + } + + @TearDown(Level.Iteration) + def iterationTeardown(params: IterationParams) { + if (params.getType == IterationType.MEASUREMENT) { + // limit to smaller cases to avoid OOM + _memory = + if (_mapEntries <= 1000000) GraphLayout.parseInstance(maps(0), maps.tail).totalSize + else 0 + } + } + } + + /** Abstract state container for the `get()` bulk calling tests. + * + * Provides a thread-scoped map of the expected size. + * Performs a GC after loading the map. + * + * @tparam K type of the map keys to be used in the test + */ + @State(Scope.Thread) + private[this] abstract class BulkGetState[K](implicit keyBuilder: KeySeqBuilder[K]) { + /** The sequence of keys to store into a map. */ + private[this] var _keys: KeySeq[K] = _ + def keys() = _keys + + val map = new OpenHashMap[K,Int].empty + + /** Load the map with keys from `1` to `size`. */ + @Setup + def setup(params: BenchmarkParams) { + val size = params.getParam("size").toInt + _keys = keyBuilder.build(size) + put(map, keys, 0, size) + System.gc() + } + } + + /** Abstract state container for the `get()` bulk calling tests with deleted entries. + * + * Provides a thread-scoped map of the expected size, from which entries have been removed. + * Performs a GC after loading the map. + * + * @tparam K type of the map keys to be used in the test + */ + @State(Scope.Thread) + private[this] abstract class BulkRemovedGetState[K](implicit keyBuilder: KeySeqBuilder[K]) { + /** The sequence of keys to store into a map. */ + private[this] var _keys: KeySeq[K] = _ + def keys() = _keys + + val map = new OpenHashMap[K,Int].empty + + /** Load the map with keys from `1` to `size`, removing half of them. */ + @Setup + def setup(params: BenchmarkParams) { + val size = params.getParam("size").toInt + _keys = keyBuilder.build(size) + put_remove(map, keys) + System.gc() + } + } + + /* In order to use `@AuxCounters` on a class hierarchy (as of JMH 1.11.3), + * it's necessary to place it on the injected (sub)class, and to make the + * counters visible as explicit public members of the that class. JMH doesn't + * scan the ancestor classes for counters. + */ + + @AuxCounters + private class IntBulkPutState extends BulkPutState[Int] { + override def mapEntries = super.mapEntries + override def operations = super.operations + override def memory = super.memory + } + private class IntBulkGetState extends BulkGetState[Int] + private class IntBulkRemovedGetState extends BulkRemovedGetState[Int] + + @AuxCounters + private class AnyRefBulkPutState extends BulkPutState[AnyRef] { + override def mapEntries = super.mapEntries + override def operations = super.operations + override def memory = super.memory + } + private class AnyRefBulkGetState extends BulkGetState[AnyRef] + private class AnyRefBulkRemovedGetState extends BulkRemovedGetState[AnyRef] + + + /** Put entries into the given map. + * Adds entries using a range of keys from the given list. + * + * @param from lowest index in the range of keys to add + * @param to highest index in the range of keys to add, plus one + */ + private[this] def put[K](map: OpenHashMap[K,Int], keys: KeySeq[K], from: Int, to: Int) { + var i = from + while (i < to) { // using a `for` expression instead adds significant overhead + map.put(keys(i), i) + i += 1 + } + } + + /** Put entries into the given map. + * Adds entries using all of the keys from the given list. + */ + private def put[K](map: OpenHashMap[K,Int], keys: KeySeq[K]): Unit = + put(map, keys, 0, keys.size) + + /** Put entries into the given map, removing half of them as they're added. + * + * @param keys list of keys to use + */ + private def put_remove[K](map: OpenHashMap[K,Int], keys: KeySeq[K]) { + val blocks = 25 // should be a non-trivial factor of `size` + val size = keys.size + val blockSize: Int = size / blocks + var base = 0 + while (base < size) { + put(map, keys, base, base + blockSize) + + // remove every other entry + var i = base + while (i < base + blockSize) { + map.remove(keys(i)) + i += 2 + } + + base += blockSize + } + } + + /** Get elements from the given map. */ + private def get[K](map: OpenHashMap[K,Int], keys: KeySeq[K]) = { + val size = keys.size + var i = 0 + var sum = 0 + while (i < size) { + sum += map.get(keys(i)).getOrElse(0) + i += 1 + } + sum + } +} + +/** Benchmark for the library's [[OpenHashMap]]. */ +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(5) +@Threads(1) +@Warmup(iterations = 20) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class OpenHashMapBenchmark { + import OpenHashMapBenchmark._ + + @Param(Array("50", "100", "1000", "10000", "100000", "1000000", "2500000", + "5000000", "7500000", "10000000", "25000000")) + var size: Int = _ + + // Tests with Int keys + + /** Test putting elements to a map of `Int` to `Int`. */ + @Benchmark + def put_Int(state: IntBulkPutState) { + var i = 0 + while (i < state.maps.length) { + put(state.maps(i), state.keys) + i += 1 + } + } + + /** Test putting and removing elements to a growing map of `Int` to `Int`. */ + @Benchmark + def put_remove_Int(state: IntBulkPutState) { + var i = 0 + while (i < state.maps.length) { + put_remove(state.maps(i), state.keys) + i += 1 + } + } + + /** Test getting elements from a map of `Int` to `Int`. */ + @Benchmark + def get_Int_after_put(state: IntBulkGetState) = + get(state.map, state.keys) + + /** Test getting elements from a map of `Int` to `Int` from which elements have been removed. + * Note that half of these queries will fail to find their keys, which have been removed. + */ + @Benchmark + def get_Int_after_put_remove(state: IntBulkRemovedGetState) = + get(state.map, state.keys) + + + // Tests with AnyRef keys + + /** Test putting elements to a map of `AnyRef` to `Int`. */ + @Benchmark + def put_AnyRef(state: AnyRefBulkPutState) { + var i = 0 + while (i < state.maps.length) { + put(state.maps(i), state.keys) + i += 1 + } + } + + /** Test putting and removing elements to a growing map of `AnyRef` to `Int`. */ + @Benchmark + def put_remove_AnyRef(state: AnyRefBulkPutState) { + var i = 0 + while (i < state.maps.length) { + put_remove(state.maps(i), state.keys) + i += 1 + } + } + + /** Test getting elements from a map of `AnyRef` to `Int`. */ + @Benchmark + def get_AnyRef_after_put(state: AnyRefBulkGetState) = + get(state.map, state.keys) + + /** Test getting elements from a map of `AnyRef` to `Int` from which elements have been removed. + * Note that half of these queries will fail to find their keys, which have been removed. + */ + @Benchmark + def get_AnyRef_after_put_remove(state: AnyRefBulkRemovedGetState) = + get(state.map, state.keys) +} diff --git a/test/benchmarks/src/main/scala/scala/collection/mutable/OpenHashMapRunner.scala b/test/benchmarks/src/main/scala/scala/collection/mutable/OpenHashMapRunner.scala new file mode 100644 index 0000000000..b14b733a81 --- /dev/null +++ b/test/benchmarks/src/main/scala/scala/collection/mutable/OpenHashMapRunner.scala @@ -0,0 +1,113 @@ +package scala.collection.mutable + +import java.io.File +import java.io.PrintWriter + +import scala.language.existentials + +import org.openjdk.jmh.results.Result +import org.openjdk.jmh.results.RunResult +import org.openjdk.jmh.runner.Runner +import org.openjdk.jmh.runner.options.CommandLineOptions +import org.openjdk.jmh.runner.options.OptionsBuilder +import org.openjdk.jmh.runner.options.VerboseMode + +import benchmark.JmhRunner + +/** Replacement JMH application that runs the [[OpenHashMap]] benchmark. + * + * Outputs the results in a form consumable by a Gnuplot script. + */ +object OpenHashMapRunner extends JmhRunner { + /** File that will be created for the output data set. */ + private[this] val outputFile = new File(outputDirectory, "OpenHashMap.dat") + + /** Qualifier to add to the name of a memory usage data set. */ + private[this] val memoryDatasetQualifier = "-memory" + + /** Adapter to the JMH result class that simplifies our method calls. */ + private[this] implicit class MyRunResult(r: RunResult) { + /** Return the dataset label. */ + def label = r.getPrimaryResult.getLabel + + /** Return the value of the JMH parameter for the number of map entries per invocation. */ + def size: String = r.getParams.getParam("size") + + /** Return the operation counts. Not every test tracks this. */ + def operations = Option(r.getSecondaryResults.get("operations")) + + /** Return the number of map entries. */ + def entries = r.getSecondaryResults.get("mapEntries") + + /** Return the memory usage. Only defined if memory usage was measured. */ + def memory = Option(r.getSecondaryResults.get("memory")) + } + + /** Return the statistics of the given result as a string. */ + private[this] def stats(r: Result[_]) = r.getScore + " " + r.getStatistics.getStandardDeviation + + + def main(args: Array[String]) { + import scala.collection.JavaConversions._ + + val opts = new CommandLineOptions(args: _*) + var builder = new OptionsBuilder().parent(opts).jvmArgsPrepend("-Xmx6000m") + if (!opts.verbosity.hasValue) builder = builder.verbosity(VerboseMode.SILENT) + + val results = new Runner(builder.build).run() + + /* Sort the JMH results into "data sets", each representing a complete test of one feature. + * Some results only measure CPU performance; while others also measure memory usage, and + * thus are split into two data sets. A data set is distinguished by its label, which is + * the label of the JMH result, for CPU performance, or that with an added suffix, for memory + * usage. + */ + + /** Map from data set name to data set. */ + val datasetByName = Map.empty[String, Set[RunResult]] + + /** Ordering for the results within a data set. Orders by increasing number of map entries. */ + val ordering = Ordering.by[RunResult, Int](_.size.toInt) + + def addToDataset(key: String, result: RunResult): Unit = + datasetByName.getOrElseUpdate(key, SortedSet.empty(ordering)) += result + + results.foreach { result => + addToDataset(result.label, result) + + // Create another data set for trials that track memory usage + if (result.memory.isDefined) + addToDataset(result.label + memoryDatasetQualifier, result) + } + + //TODO Write out test parameters + // val jvm = params.getJvm + // val jvmArgs = params.getJvmArgs.mkString(" ") + + val f = new PrintWriter(outputFile, "UTF-8") + try { + datasetByName.foreach(_ match { + case (label: String, dataset: Iterable[RunResult]) => + outputDataset(f, label, dataset) + }) + } finally { + f.close() + } + } + + private[this] def outputDataset(f: PrintWriter, label: String, dataset: Iterable[RunResult]) { + f.println(s"# [$label]") + + val isMemoryUsageDataset = label.endsWith(memoryDatasetQualifier) + dataset.foreach { r => + f.println(r.size + " " + ( + if (isMemoryUsageDataset && !r.memory.get.getScore.isInfinite) + stats(r.entries) + " " + stats(r.memory.get) + else + stats(r.operations getOrElse r.getPrimaryResult) + )) + } + + f.println(); f.println() // data set separator + } +} diff --git a/test/files/neg/eta-expand-star.check b/test/files/neg/eta-expand-star.check index 6765d504fc..eba1721014 100644 --- a/test/files/neg/eta-expand-star.check +++ b/test/files/neg/eta-expand-star.check @@ -1,4 +1,4 @@ -eta-expand-star.scala:6: error: too many arguments for method apply: (v1: Seq[T])Unit in trait Function1 +eta-expand-star.scala:6: error: too many arguments (2) for method apply: (v1: Seq[T])Unit in trait Function1 g(1, 2) - ^ + ^ one error found diff --git a/test/files/neg/macro-invalidusage-badargs.check b/test/files/neg/macro-invalidusage-badargs.check index 19ac6528d3..ee549c45cb 100644 --- a/test/files/neg/macro-invalidusage-badargs.check +++ b/test/files/neg/macro-invalidusage-badargs.check @@ -13,7 +13,7 @@ Macros_Test_2.scala:8: error: not enough arguments for macro method foo: (x: Int Unspecified value parameter x. foo() ^ -Macros_Test_2.scala:9: error: too many arguments for macro method foo: (x: Int)Int +Macros_Test_2.scala:9: error: too many arguments (2) for macro method foo: (x: Int)Int foo(4, 2) - ^ + ^ 5 errors found diff --git a/test/files/neg/multi-array.check b/test/files/neg/multi-array.check index 511caa126f..06ffdc9fbc 100644 --- a/test/files/neg/multi-array.check +++ b/test/files/neg/multi-array.check @@ -1,4 +1,4 @@ -multi-array.scala:7: error: too many arguments for constructor Array: (_length: Int)Array[T] +multi-array.scala:7: error: too many arguments (2) for constructor Array: (_length: Int)Array[T] val a: Array[Int] = new Array(10, 10) - ^ + ^ one error found diff --git a/test/files/neg/protected-constructors.check b/test/files/neg/protected-constructors.check index f44d7db9b9..0279f5815d 100644 --- a/test/files/neg/protected-constructors.check +++ b/test/files/neg/protected-constructors.check @@ -1,6 +1,6 @@ -protected-constructors.scala:17: error: too many arguments for constructor Foo1: ()dingus.Foo1 +protected-constructors.scala:17: error: no arguments allowed for nullary constructor Foo1: ()dingus.Foo1 val foo1 = new Foo1("abc") - ^ + ^ protected-constructors.scala:18: error: constructor Foo2 in class Foo2 cannot be accessed in object P Access to protected constructor Foo2 not permitted because enclosing object P in package hungus is not a subclass of @@ -19,4 +19,7 @@ protected-constructors.scala:15: error: class Foo3 in object Ding cannot be acce object Ding in package dingus where target is defined class Bar3 extends Ding.Foo3("abc") ^ -four errors found +protected-constructors.scala:15: error: no arguments allowed for nullary constructor Object: ()Object + class Bar3 extends Ding.Foo3("abc") + ^ +5 errors found diff --git a/test/files/neg/t1112.check b/test/files/neg/t1112.check index 5e3821b153..e6058bf176 100644 --- a/test/files/neg/t1112.check +++ b/test/files/neg/t1112.check @@ -1,4 +1,4 @@ -t1112.scala:12: error: too many arguments for method call: (p: Int)(f: => Test.this.Type1)Unit +t1112.scala:12: error: too many arguments (2) for method call: (p: Int)(f: => Test.this.Type1)Unit call(0,() => System.out.println("here we are")) - ^ + ^ one error found diff --git a/test/files/neg/t1523.check b/test/files/neg/t1523.check index d2489f2602..273d0f8cf7 100644 --- a/test/files/neg/t1523.check +++ b/test/files/neg/t1523.check @@ -1,4 +1,4 @@ -t1523.scala:4: error: too many arguments for method bug: (x: Any)Any +t1523.scala:4: error: 25 more arguments than can be applied to method bug: (x: Any)Any def go() = bug("a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a") - ^ + ^ one error found diff --git a/test/files/neg/t6920.check b/test/files/neg/t6920.check index ee4eafb83e..d10abff03c 100644 --- a/test/files/neg/t6920.check +++ b/test/files/neg/t6920.check @@ -1,6 +1,6 @@ -t6920.scala:9: error: too many arguments for method applyDynamicNamed: (values: Seq[(String, Any)])String +t6920.scala:9: error: too many arguments (2) for method applyDynamicNamed: (values: Seq[(String, Any)])String error after rewriting to CompilerError.this.test.applyDynamicNamed("crushTheCompiler")(scala.Tuple2("a", 1), scala.Tuple2("b", 2)) possible cause: maybe a wrong Dynamic method signature? test.crushTheCompiler(a = 1, b = 2) - ^ + ^ one error found diff --git a/test/files/neg/t7157.check b/test/files/neg/t7157.check index 3988460d4b..0b81394946 100644 --- a/test/files/neg/t7157.check +++ b/test/files/neg/t7157.check @@ -1,22 +1,22 @@ -Test_2.scala:5: error: too many arguments for macro method m1_0_0: ()Unit +Test_2.scala:5: error: no arguments allowed for nullary macro method m1_0_0: ()Unit m1_0_0(1) - ^ -Test_2.scala:6: error: too many arguments for macro method m1_0_0: ()Unit + ^ +Test_2.scala:6: error: no arguments allowed for nullary macro method m1_0_0: ()Unit m1_0_0(1, 2) - ^ -Test_2.scala:7: error: too many arguments for macro method m1_0_0: ()Unit + ^ +Test_2.scala:7: error: no arguments allowed for nullary macro method m1_0_0: ()Unit m1_0_0(1, 2, 3) - ^ + ^ Test_2.scala:9: error: not enough arguments for macro method m1_1_1: (x: Int)Unit. Unspecified value parameter x. m1_1_1() ^ -Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit +Test_2.scala:11: error: too many arguments (2) for macro method m1_1_1: (x: Int)Unit m1_1_1(1, 2) - ^ -Test_2.scala:12: error: too many arguments for macro method m1_1_1: (x: Int)Unit + ^ +Test_2.scala:12: error: too many arguments (3) for macro method m1_1_1: (x: Int)Unit m1_1_1(1, 2, 3) - ^ + ^ Test_2.scala:14: error: not enough arguments for macro method m1_2_2: (x: Int, y: Int)Unit. Unspecified value parameters x, y. m1_2_2() @@ -25,9 +25,9 @@ Test_2.scala:15: error: not enough arguments for macro method m1_2_2: (x: Int, y Unspecified value parameter y. m1_2_2(1) ^ -Test_2.scala:17: error: too many arguments for macro method m1_2_2: (x: Int, y: Int)Unit +Test_2.scala:17: error: too many arguments (3) for macro method m1_2_2: (x: Int, y: Int)Unit m1_2_2(1, 2, 3) - ^ + ^ Test_2.scala:24: error: not enough arguments for macro method m1_1_inf: (x: Int, y: Int*)Unit. Unspecified value parameters x, y. m1_1_inf() @@ -40,25 +40,25 @@ Test_2.scala:30: error: not enough arguments for macro method m1_2_inf: (x: Int, Unspecified value parameters y, z. m1_2_inf(1) ^ -Test_2.scala:35: error: too many arguments for macro method m2_0_0: ()Unit +Test_2.scala:35: error: no arguments allowed for nullary macro method m2_0_0: ()Unit m2_0_0()(1) - ^ -Test_2.scala:36: error: too many arguments for macro method m2_0_0: ()Unit + ^ +Test_2.scala:36: error: no arguments allowed for nullary macro method m2_0_0: ()Unit m2_0_0()(1, 2) - ^ -Test_2.scala:37: error: too many arguments for macro method m2_0_0: ()Unit + ^ +Test_2.scala:37: error: no arguments allowed for nullary macro method m2_0_0: ()Unit m2_0_0()(1, 2, 3) - ^ + ^ Test_2.scala:39: error: not enough arguments for macro method m2_1_1: (x: Int)Unit. Unspecified value parameter x. m2_1_1()() ^ -Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit +Test_2.scala:41: error: too many arguments (2) for macro method m2_1_1: (x: Int)Unit m2_1_1()(1, 2) - ^ -Test_2.scala:42: error: too many arguments for macro method m2_1_1: (x: Int)Unit + ^ +Test_2.scala:42: error: too many arguments (3) for macro method m2_1_1: (x: Int)Unit m2_1_1()(1, 2, 3) - ^ + ^ Test_2.scala:44: error: not enough arguments for macro method m2_2_2: (x: Int, y: Int)Unit. Unspecified value parameters x, y. m2_2_2()() @@ -67,9 +67,9 @@ Test_2.scala:45: error: not enough arguments for macro method m2_2_2: (x: Int, y Unspecified value parameter y. m2_2_2()(1) ^ -Test_2.scala:47: error: too many arguments for macro method m2_2_2: (x: Int, y: Int)Unit +Test_2.scala:47: error: too many arguments (3) for macro method m2_2_2: (x: Int, y: Int)Unit m2_2_2()(1, 2, 3) - ^ + ^ Test_2.scala:54: error: not enough arguments for macro method m2_1_inf: (x: Int, y: Int*)Unit. Unspecified value parameters x, y. m2_1_inf()() diff --git a/test/files/neg/t8006.check b/test/files/neg/t8006.check index fbac26e3ad..6152d0fba3 100644 --- a/test/files/neg/t8006.check +++ b/test/files/neg/t8006.check @@ -1,6 +1,6 @@ -t8006.scala:3: error: too many arguments for method applyDynamicNamed: (value: (String, Any))String +t8006.scala:3: error: too many arguments (2) for method applyDynamicNamed: (value: (String, Any))String error after rewriting to X.this.d.applyDynamicNamed("meth")(scala.Tuple2("value1", 10), scala.Tuple2("value2", 100)) possible cause: maybe a wrong Dynamic method signature? d.meth(value1 = 10, value2 = 100) // two arguments here, but only one is allowed - ^ + ^ one error found diff --git a/test/files/neg/t8035-no-adapted-args.check b/test/files/neg/t8035-no-adapted-args.check index 43637b2c1f..0115dddc91 100644 --- a/test/files/neg/t8035-no-adapted-args.check +++ b/test/files/neg/t8035-no-adapted-args.check @@ -4,9 +4,9 @@ t8035-no-adapted-args.scala:4: warning: No automatic adaptation here: use explic after adaptation: Test.f((1, 2, 3): (Int, Int, Int)) f(1, 2, 3) ^ -t8035-no-adapted-args.scala:4: error: too many arguments for method f: (x: (Int, Int, Int))Int +t8035-no-adapted-args.scala:4: error: too many arguments (3) for method f: (x: (Int, Int, Int))Int f(1, 2, 3) - ^ + ^ t8035-no-adapted-args.scala:5: warning: No automatic adaptation here: use explicit parentheses. signature: Test.f[T](x: T): Int given arguments: <none> diff --git a/test/files/neg/t8667.check b/test/files/neg/t8667.check new file mode 100644 index 0000000000..82451ee5d6 --- /dev/null +++ b/test/files/neg/t8667.check @@ -0,0 +1,91 @@ +t8667.scala:6: error: too many arguments (3) for constructor C: (a: Int, b: Int)C +Note that 'c' is not a parameter name of the invoked method. + def c2 = new C(a = 42, b = 17, c = 5) + ^ +t8667.scala:7: error: unknown parameter name: c + def c3 = new C(b = 42, a = 17, c = 5) + ^ +t8667.scala:7: error: too many arguments (3) for constructor C: (a: Int, b: Int)C + def c3 = new C(b = 42, a = 17, c = 5) + ^ +t8667.scala:8: error: positional after named argument. + def c4 = new C(b = 42, a = 17, 5) + ^ +t8667.scala:8: error: too many arguments (3) for constructor C: (a: Int, b: Int)C + def c4 = new C(b = 42, a = 17, 5) + ^ +t8667.scala:9: error: not found: value c + def c5 = new C(a = 42, c = 17) + ^ +t8667.scala:10: error: parameter 'b' is already specified at parameter position 2 +Note that 'c' is not a parameter name of the invoked method. + def c6 = new C(a = 42, c = 17, b = 5) + ^ +t8667.scala:10: error: too many arguments (3) for constructor C: (a: Int, b: Int)C +Note that 'c' is not a parameter name of the invoked method. + def c6 = new C(a = 42, c = 17, b = 5) + ^ +t8667.scala:11: error: parameter 'a' is already specified at parameter position 1 +Note that 'c' is not a parameter name of the invoked method. + def c7 = new C(c = 42, a = 17, b = 5) + ^ +t8667.scala:11: error: too many arguments (3) for constructor C: (a: Int, b: Int)C +Note that 'c' is not a parameter name of the invoked method. + def c7 = new C(c = 42, a = 17, b = 5) + ^ +t8667.scala:12: error: parameter 'b' is already specified at parameter position 2 + def c8 = new C(42, 17, b = 5) + ^ +t8667.scala:12: error: too many arguments (3) for constructor C: (a: Int, b: Int)C + def c8 = new C(42, 17, b = 5) + ^ +t8667.scala:13: error: parameter 'b' is already specified at parameter position 2 +Note that 'c' is not a parameter name of the invoked method. + def c9 = new C(a = 42, c = 17, d = 3, b = 5) + ^ +t8667.scala:13: error: too many arguments (4) for constructor C: (a: Int, b: Int)C +Note that 'c', 'd' are not parameter names of the invoked method. + def c9 = new C(a = 42, c = 17, d = 3, b = 5) + ^ +t8667.scala:14: error: too many arguments (4) for constructor C: (a: Int, b: Int)C +Note that 'd', 'c' are not parameter names of the invoked method. + def c0 = new C(42, 17, d = 3, c = 5) + ^ +t8667.scala:25: error: no arguments allowed for nullary method f0: ()Int + f0(1) + ^ +t8667.scala:26: error: too many arguments (2) for method f1: (i: Int)Int + f1(1, 2) + ^ +t8667.scala:27: error: too many arguments (3) for method f1: (i: Int)Int + f1(1, 2, 3) + ^ +t8667.scala:28: error: 3 more arguments than can be applied to method f1: (i: Int)Int + f1(1, 2, 3, 4) + ^ +t8667.scala:29: error: 3 more arguments than can be applied to method f1: (i: Int)Int +Note that 'j' is not a parameter name of the invoked method. + f1(1, j = 2, 3, 4) + ^ +t8667.scala:30: error: 3 more arguments than can be applied to method f1: (i: Int)Int +Note that 'j', 'k' are not parameter names of the invoked method. + f1(1, j = 2, k = 3, 4) + ^ +t8667.scala:31: error: parameter 'i' is already specified at parameter position 1 +Note that 'k' is not a parameter name of the invoked method. + f2(k = 1, i = 2, j = 3) + ^ +t8667.scala:31: error: too many arguments (3) for method f2: (i: Int, j: Int)Int +Note that 'k' is not a parameter name of the invoked method. + f2(k = 1, i = 2, j = 3) + ^ +t8667.scala:32: error: one more argument than can be applied to method f6: (i: Int, j: Int, k: Int, l: Int, m: Int, n: Int)Int + f6(1, 2, 3, 4, 5, 6, 7) + ^ +t8667.scala:33: error: 2 more arguments than can be applied to method f6: (i: Int, j: Int, k: Int, l: Int, m: Int, n: Int)Int + f6(1, 2, 3, 4, 5, 6, 7, 8) + ^ +t8667.scala:34: error: 15 arguments but expected 12 for method f12: (i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int, q: Int, r: Int, s: Int, t: Int)Int + f12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + ^ +26 errors found diff --git a/test/files/neg/t8667.scala b/test/files/neg/t8667.scala new file mode 100644 index 0000000000..d55582ca6b --- /dev/null +++ b/test/files/neg/t8667.scala @@ -0,0 +1,37 @@ + +class C(a: Int, b: Int) + +trait T { + def c1 = new C(a = 42, b = 17) + def c2 = new C(a = 42, b = 17, c = 5) + def c3 = new C(b = 42, a = 17, c = 5) + def c4 = new C(b = 42, a = 17, 5) + def c5 = new C(a = 42, c = 17) + def c6 = new C(a = 42, c = 17, b = 5) + def c7 = new C(c = 42, a = 17, b = 5) + def c8 = new C(42, 17, b = 5) + def c9 = new C(a = 42, c = 17, d = 3, b = 5) + def c0 = new C(42, 17, d = 3, c = 5) +} + +trait X { + def f0() = 42 + def f1(i: Int) = 42 + def f2(i: Int, j: Int) = 42 + def f6(i: Int, j: Int, k: Int, l: Int, m: Int, n: Int) = 42 + def f12(i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int, q: Int, r: Int, s: Int, t: Int) = 42 + + def g() = { + f0(1) + f1(1, 2) + f1(1, 2, 3) + f1(1, 2, 3, 4) + f1(1, j = 2, 3, 4) + f1(1, j = 2, k = 3, 4) + f2(k = 1, i = 2, j = 3) + f6(1, 2, 3, 4, 5, 6, 7) + f6(1, 2, 3, 4, 5, 6, 7, 8) + f12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + () + } +} diff --git a/test/files/neg/t876.check b/test/files/neg/t876.check index 04c5c8f22e..7df2e126a6 100644 --- a/test/files/neg/t876.check +++ b/test/files/neg/t876.check @@ -1,4 +1,4 @@ -t876.scala:25: error: too many arguments for method apply: (key: AssertionError.A)manager.B in class HashMap +t876.scala:25: error: too many arguments (2) for method apply: (key: AssertionError.A)manager.B in class HashMap assert(manager.map(A2) == List(manager.map(A2, A1))) - ^ + ^ one error found diff --git a/test/files/neg/trait-no-native.check b/test/files/neg/trait-no-native.check new file mode 100644 index 0000000000..12bce4042d --- /dev/null +++ b/test/files/neg/trait-no-native.check @@ -0,0 +1,4 @@ +trait-no-native.scala:3: error: A trait cannot define a native method. + @native def foo = ??? + ^ +one error found diff --git a/test/files/neg/trait-no-native.scala b/test/files/neg/trait-no-native.scala new file mode 100644 index 0000000000..463e604a48 --- /dev/null +++ b/test/files/neg/trait-no-native.scala @@ -0,0 +1,4 @@ +trait T { + // should not compile, because it would result in a VerifyError + @native def foo = ??? +} diff --git a/test/files/t8449/Client.scala b/test/files/pos/t8449/Client.scala index 5d273f06b2..5d273f06b2 100644 --- a/test/files/t8449/Client.scala +++ b/test/files/pos/t8449/Client.scala diff --git a/test/files/t8449/Test.java b/test/files/pos/t8449/Test.java index ecb1711b24..ecb1711b24 100644 --- a/test/files/t8449/Test.java +++ b/test/files/pos/t8449/Test.java diff --git a/test/files/run/repl-paste-b.check b/test/files/run/repl-paste-b.check new file mode 100644 index 0000000000..2e205d48d6 --- /dev/null +++ b/test/files/run/repl-paste-b.check @@ -0,0 +1,14 @@ + +scala> :paste < EOF +// Entering paste mode (EOF to finish) + +object X +EOF + +// Exiting paste mode, now interpreting. + +defined object X + +scala> assert(X.getClass.getName.contains("line")) + +scala> :quit diff --git a/test/files/run/repl-paste-b.scala b/test/files/run/repl-paste-b.scala new file mode 100644 index 0000000000..718f7d9e17 --- /dev/null +++ b/test/files/run/repl-paste-b.scala @@ -0,0 +1,13 @@ +import scala.tools.partest.ReplTest + +// confirm X not in empty package +object Test extends ReplTest { + def code = + """ +:paste < EOF +object X +EOF +assert(X.getClass.getName.contains("line")) +""" + +} diff --git a/test/files/run/repl-paste-parse.check b/test/files/run/repl-paste-parse.check new file mode 100755 index 0000000000..7b2148dc74 --- /dev/null +++ b/test/files/run/repl-paste-parse.check @@ -0,0 +1,6 @@ +Type in expressions for evaluation. Or try :help. + +scala> repl-paste-parse.script:1: error: illegal start of simple pattern +val case = 9 + ^ +:quit diff --git a/test/files/run/repl-paste-parse.scala b/test/files/run/repl-paste-parse.scala new file mode 100644 index 0000000000..e93ad4d02b --- /dev/null +++ b/test/files/run/repl-paste-parse.scala @@ -0,0 +1,27 @@ + +import java.io.{ BufferedReader, StringReader, StringWriter, PrintWriter } + +import scala.tools.partest.DirectTest +import scala.tools.nsc.interpreter.ILoop +import scala.tools.nsc.GenericRunnerSettings + +object Test extends DirectTest { + override def extraSettings = s"-usejavacp -i $scriptPath" + def scriptPath = testPath.changeExtension("script") + override def newSettings(args: List[String]) = { + val ss = new GenericRunnerSettings(Console.println) + ss.processArguments(args, true) + ss + } + def code = "" + def show() = { + val r = new BufferedReader(new StringReader("")) + val w = new StringWriter + val p = new PrintWriter(w, true) + new ILoop(r, p).process(settings) + w.toString.lines foreach { s => + if (!s.startsWith("Welcome to Scala")) println(s) + } + } +} + diff --git a/test/files/run/repl-paste-parse.script b/test/files/run/repl-paste-parse.script new file mode 100644 index 0000000000..903f6e7b0c --- /dev/null +++ b/test/files/run/repl-paste-parse.script @@ -0,0 +1 @@ +val case = 9 diff --git a/test/files/run/repl-paste-raw-b.pastie b/test/files/run/repl-paste-raw-b.pastie new file mode 100644 index 0000000000..f13b4bcf8b --- /dev/null +++ b/test/files/run/repl-paste-raw-b.pastie @@ -0,0 +1,8 @@ + +// a raw paste is not a script +// hence it can be packaged + +package brown_paper + +// these are a few of my favorite things +case class Gift (hasString: Boolean) diff --git a/test/files/run/repl-paste-raw-b.scala b/test/files/run/repl-paste-raw-b.scala new file mode 100644 index 0000000000..d1c7692f2f --- /dev/null +++ b/test/files/run/repl-paste-raw-b.scala @@ -0,0 +1,18 @@ + +import scala.tools.partest.SessionTest + +object Test extends SessionTest { + def session = +s"""| + |scala> :paste $pastie + |Pasting file $pastie... + | + |scala> val favoriteThing = brown_paper.Gift(true) + |favoriteThing: brown_paper.Gift = Gift(true) + | + |scala> favoriteThing.hasString + |res0: Boolean = true + | + |scala> :quit""" + def pastie = testPath changeExtension "pastie" +} diff --git a/test/files/run/repl-paste-raw-c.pastie b/test/files/run/repl-paste-raw-c.pastie new file mode 100644 index 0000000000..364d8cef4b --- /dev/null +++ b/test/files/run/repl-paste-raw-c.pastie @@ -0,0 +1,5 @@ + +// not actually a candidate for raw paste + +val nope = 42 + diff --git a/test/files/run/repl-paste-raw-c.scala b/test/files/run/repl-paste-raw-c.scala new file mode 100644 index 0000000000..600ac4d2f0 --- /dev/null +++ b/test/files/run/repl-paste-raw-c.scala @@ -0,0 +1,16 @@ + +import scala.tools.partest.SessionTest + +object Test extends SessionTest { + def session = +s"""| + |scala> :paste -raw $pastie + |Pasting file $pastie... + |$pastie:3: error: expected class or object definition + |val nope = 42 + |^ + |There were compilation errors! + | + |scala> :quit""" + def pastie = testPath changeExtension "pastie" +} diff --git a/test/files/run/repl-paste-raw.pastie b/test/files/run/repl-paste-raw.pastie index f13b4bcf8b..a4a570aaa2 100644 --- a/test/files/run/repl-paste-raw.pastie +++ b/test/files/run/repl-paste-raw.pastie @@ -1,8 +1,8 @@ +package brown_paper + // a raw paste is not a script // hence it can be packaged -package brown_paper - // these are a few of my favorite things case class Gift (hasString: Boolean) diff --git a/test/files/run/repl-paste-raw.scala b/test/files/run/repl-paste-raw.scala index 9bd5e8e63e..d1c7692f2f 100644 --- a/test/files/run/repl-paste-raw.scala +++ b/test/files/run/repl-paste-raw.scala @@ -4,7 +4,7 @@ import scala.tools.partest.SessionTest object Test extends SessionTest { def session = s"""| - |scala> :paste -raw $pastie + |scala> :paste $pastie |Pasting file $pastie... | |scala> val favoriteThing = brown_paper.Gift(true) diff --git a/test/files/run/t4625.check b/test/files/run/t4625.check new file mode 100644 index 0000000000..e4a4d15b87 --- /dev/null +++ b/test/files/run/t4625.check @@ -0,0 +1 @@ +Test ran. diff --git a/test/files/run/t4625.scala b/test/files/run/t4625.scala new file mode 100644 index 0000000000..44f6225220 --- /dev/null +++ b/test/files/run/t4625.scala @@ -0,0 +1,7 @@ + +import scala.tools.partest.ScriptTest + +object Test extends ScriptTest { + // must be called Main to get probing treatment in parser + override def testmain = "Main" +} diff --git a/test/files/run/t4625.script b/test/files/run/t4625.script new file mode 100644 index 0000000000..600ceacbb6 --- /dev/null +++ b/test/files/run/t4625.script @@ -0,0 +1,5 @@ + +object Main extends Runnable with App { + def run() = println("Test ran.") + run() +} diff --git a/test/files/run/t4625b.check b/test/files/run/t4625b.check new file mode 100644 index 0000000000..e79539a5c4 --- /dev/null +++ b/test/files/run/t4625b.check @@ -0,0 +1 @@ +Misc top-level detritus diff --git a/test/files/run/t4625b.scala b/test/files/run/t4625b.scala new file mode 100644 index 0000000000..44f6225220 --- /dev/null +++ b/test/files/run/t4625b.scala @@ -0,0 +1,7 @@ + +import scala.tools.partest.ScriptTest + +object Test extends ScriptTest { + // must be called Main to get probing treatment in parser + override def testmain = "Main" +} diff --git a/test/files/run/t4625b.script b/test/files/run/t4625b.script new file mode 100644 index 0000000000..f21a553dd1 --- /dev/null +++ b/test/files/run/t4625b.script @@ -0,0 +1,8 @@ + +trait X { def x = "Misc top-level detritus" } + +object Bumpkus + +object Main extends X with App { + println(x) +} diff --git a/test/files/run/t4625c.check b/test/files/run/t4625c.check new file mode 100644 index 0000000000..6acb1710b9 --- /dev/null +++ b/test/files/run/t4625c.check @@ -0,0 +1,3 @@ +newSource1.scala:2: warning: Script has a main object but statement is disallowed +val x = "value x" + ^ diff --git a/test/files/run/t4625c.scala b/test/files/run/t4625c.scala new file mode 100644 index 0000000000..44f6225220 --- /dev/null +++ b/test/files/run/t4625c.scala @@ -0,0 +1,7 @@ + +import scala.tools.partest.ScriptTest + +object Test extends ScriptTest { + // must be called Main to get probing treatment in parser + override def testmain = "Main" +} diff --git a/test/files/run/t4625c.script b/test/files/run/t4625c.script new file mode 100644 index 0000000000..16159208e0 --- /dev/null +++ b/test/files/run/t4625c.script @@ -0,0 +1,7 @@ + +val x = "value x" +val y = "value y" + +object Main extends App { + println(s"Test ran with $x.") +} diff --git a/test/files/run/t7805-repl-i.check b/test/files/run/t7805-repl-i.check index 24512c0067..70f024605c 100644 --- a/test/files/run/t7805-repl-i.check +++ b/test/files/run/t7805-repl-i.check @@ -1,6 +1,3 @@ -Loading t7805-repl-i.script... -import util._ - Welcome to Scala Type in expressions for evaluation. Or try :help. diff --git a/test/files/run/t9170.scala b/test/files/run/t9170.scala index f39467bc25..87471fb129 100644 --- a/test/files/run/t9170.scala +++ b/test/files/run/t9170.scala @@ -44,7 +44,7 @@ object Y { // Exiting paste mode, now interpreting. -<console>:13: error: double definition: +<pastie>:13: error: double definition: def f[A](a: => A): Int at line 12 and def f[A](a: => Either[Exception,A]): Int at line 13 have same type after erasure: (a: Function0)Int diff --git a/test/junit/scala/collection/immutable/HashMapTest.scala b/test/junit/scala/collection/immutable/HashMapTest.scala new file mode 100644 index 0000000000..a970786455 --- /dev/null +++ b/test/junit/scala/collection/immutable/HashMapTest.scala @@ -0,0 +1,48 @@ +package scala.collection.immutable + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class HashMapTest { + + private val computeHashF = { + HashMap.empty.computeHash _ + } + + @Test + def canMergeIdenticalHashMap1sWithNullKvs() { + def m = new HashMap.HashMap1(1, computeHashF(1), 1, null) + val merged = m.merged(m)(null) + assertEquals(m, merged) + } + + @Test + def canMergeIdenticalHashMap1sWithNullKvsCustomMerge() { + def m = new HashMap.HashMap1(1, computeHashF(1), 1, null) + val merged = m.merged(m) { + case ((k1, v1), (k2, v2)) => + (k1, v1 + v2) + } + assertEquals(new HashMap.HashMap1(1, computeHashF(1), 2, null), merged) + } + + @Test + def canMergeHashMap1sWithNullKvsHashCollision() { + val key1 = 1000L * 1000 * 1000 * 10 + val key2 = key1.##.toLong + assert(key1.## == key2.##) + + val m1 = new HashMap.HashMap1(key1, computeHashF(key1.##), 1, null) + val m2 = new HashMap.HashMap1(key2, computeHashF(key2.##), 1, null) + val expected = HashMap(key1 -> 1, key2 -> 1) + val merged = m1.merged(m2)(null) + assertEquals(expected, merged) + val mergedWithMergeFunction = m1.merged(m2) { (kv1, kv2) => + throw new RuntimeException("Should not be reached.") + } + assertEquals(expected, mergedWithMergeFunction) + } +}
\ No newline at end of file diff --git a/test/junit/scala/collection/immutable/StringLikeTest.scala b/test/junit/scala/collection/immutable/StringLikeTest.scala index 50be638b89..44bade860e 100644 --- a/test/junit/scala/collection/immutable/StringLikeTest.scala +++ b/test/junit/scala/collection/immutable/StringLikeTest.scala @@ -1,5 +1,6 @@ package scala.collection.immutable +import org.junit.Assert._ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -40,4 +41,34 @@ class StringLikeTest { AssertUtil.assertSameElements("--ch--omp--".split("-"), Array("", "", "ch", "", "omp")) // All the cases! AssertUtil.assertSameElements(twopairs.split(high), Array(twopairs)) //don't split on characters that are half a surrogate pair } + + /* Test for SI-9767 */ + @Test + def testNumericConversion: Unit = { + val sOne = " \t\n 1 \n\r\t " + val sOk = "2" + val sNull:String = null + + AssertUtil.assertThrows[java.lang.NumberFormatException](sOne.toInt) + AssertUtil.assertThrows[java.lang.NumberFormatException](sOne.toLong) + AssertUtil.assertThrows[java.lang.NumberFormatException](sOne.toShort) + AssertUtil.assertThrows[java.lang.NumberFormatException](sOne.toByte) + assertTrue("trim toDouble", sOne.toDouble == 1.0d) + assertTrue("trim toFloat", sOne.toFloat == 1.0f) + + assertTrue("no trim toInt", sOk.toInt == 2) + assertTrue("no trim toLong", sOk.toLong == 2L) + assertTrue("no trim toShort", sOk.toShort == 2.toShort) + assertTrue("no trim toByte", sOk.toByte == 2.toByte) + assertTrue("no trim toDouble", sOk.toDouble == 2.0d) + assertTrue("no trim toFloat", sOk.toFloat == 2.0f) + + AssertUtil.assertThrows[java.lang.NumberFormatException](sNull.toInt, {s => s == "null"}) + AssertUtil.assertThrows[java.lang.NumberFormatException](sNull.toLong, {s => s == "null"}) + AssertUtil.assertThrows[java.lang.NumberFormatException](sNull.toShort, {s => s == "null"}) + AssertUtil.assertThrows[java.lang.NumberFormatException](sNull.toByte, {s => s == "null"}) + + AssertUtil.assertThrows[java.lang.NullPointerException](sNull.toDouble) + AssertUtil.assertThrows[java.lang.NullPointerException](sNull.toFloat) + } } diff --git a/test/junit/scala/lang/primitives/NaNTest.scala b/test/junit/scala/lang/primitives/NaNTest.scala new file mode 100644 index 0000000000..f4c4258395 --- /dev/null +++ b/test/junit/scala/lang/primitives/NaNTest.scala @@ -0,0 +1,38 @@ +package scala.lang.primitives + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.tools.testing.RunTesting + +@RunWith(classOf[JUnit4]) +class NaNTest extends RunTesting { + + @Test + def compNaNFalse(): Unit = { + def code(tp: String) = + s"""val n = $tp.NaN + |def ne(x: $tp, y: $tp) = x != y + |val fs: List[($tp, $tp) => Boolean] = List(_ < _, _ <= _, _ > _, _ >= _, _ == _, (x, y) => !ne(x, y)) + |val vs = List[$tp](n, 1, -1, 0) + |for (f <- fs; v <- vs; (x, y) <- List((n, v), (v, n))) yield f(x, y) + """.stripMargin + + runner.run[List[Boolean]](code("Double")).foreach(assertFalse) + runner.run[List[Boolean]](code("Float")).foreach(assertFalse) + } + + @Test + def genericEqNe(): Unit = { + def code(tp: String) = + s"""def a[T](x: T, y: T) = x == y + |def b[T](x: T, y: T) = x != y + |val n = $tp.NaN + |a(n, n) :: a(n, 0) :: a (0, n) :: !b(n, n) :: !b(n, 0) :: !b(0, n) :: Nil + """.stripMargin + runner.run[List[Boolean]](code("Double")).foreach(assertFalse) + runner.run[List[Boolean]](code("Float")).foreach(assertFalse) + } +} |