summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/CompileSocket.scala2
-rw-r--r--src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala2
-rw-r--r--src/compiler/scala/tools/nsc/settings/FscSettings.scala23
-rw-r--r--src/compiler/scala/tools/nsc/transform/LambdaLift.scala41
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala121
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala45
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala2
-rw-r--r--src/library/scala/concurrent/BatchingExecutor.scala117
-rw-r--r--src/library/scala/concurrent/Future.scala6
-rw-r--r--src/reflect/scala/reflect/internal/Symbols.scala2
-rw-r--r--src/reflect/scala/reflect/internal/Trees.scala24
-rw-r--r--src/reflect/scala/reflect/macros/Attachments.scala14
-rw-r--r--test/files/disabled/run/t4602.scala57
-rw-r--r--test/files/jvm/scala-concurrent-tck.scala7
-rw-r--r--test/files/neg/t6443c.check7
-rw-r--r--test/files/neg/t6443c.scala21
-rw-r--r--test/files/pos/t6891.flags1
-rw-r--r--test/files/pos/t6891.scala26
-rw-r--r--test/files/run/t6135.scala13
-rw-r--r--test/files/run/t6443-by-name.check3
-rw-r--r--test/files/run/t6443-by-name.scala18
-rw-r--r--test/files/run/t6443-varargs.check1
-rw-r--r--test/files/run/t6443-varargs.scala16
-rw-r--r--test/files/run/t6443.scala15
-rw-r--r--test/files/run/t6443b.scala16
-rw-r--r--test/files/run/t6863.scala114
-rwxr-xr-xtest/partest4
27 files changed, 680 insertions, 38 deletions
diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala
index 6b55537195..b9f62f49b3 100644
--- a/src/compiler/scala/tools/nsc/CompileSocket.scala
+++ b/src/compiler/scala/tools/nsc/CompileSocket.scala
@@ -68,7 +68,7 @@ class CompileSocket extends CompileOutputCommon {
/** A temporary directory to use */
val tmpDir = {
val udir = Option(Properties.userName) getOrElse "shared"
- val f = (Path(Properties.tmpDir) / "scala-devel" / udir).createDirectory()
+ val f = (Path(Properties.tmpDir) / ("scala-devel" + udir)).createDirectory()
if (f.isDirectory && f.canWrite) {
info("[Temp directory: " + f + "]")
diff --git a/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala b/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala
index 2f4975e681..8c54c4a1b0 100644
--- a/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala
+++ b/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala
@@ -33,7 +33,7 @@ class OfflineCompilerCommand(arguments: List[String], settings: FscSettings) ext
}
else {
// Otherwise we're on the server and will use it to absolutize the paths.
- settings.absolutize(currentDir.value)
+ settings.absolutize()
}
}
diff --git a/src/compiler/scala/tools/nsc/settings/FscSettings.scala b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
index 14b398e50a..34c8e8df9a 100644
--- a/src/compiler/scala/tools/nsc/settings/FscSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
@@ -39,13 +39,24 @@ class FscSettings(error: String => Unit) extends Settings(error) {
d, dependencyfile, pluginsDir, Ygenjavap
)
- /** All user set settings rewritten with absolute paths. */
- def absolutize(root: Path) {
- def rewrite(p: String) = (root resolve Path(p)).normalize.path
+ override def processArguments(arguments: List[String], processAll: Boolean): (Boolean, List[String]) = {
+ val (r, args) = super.processArguments(arguments, processAll)
+ // we need to ensure the files specified with relative locations are absolutized based on the currentDir
+ (r, args map {a => absolutizePath(a)})
+ }
+
+ /**
+ * Take an individual path and if it's not absolute turns it into an absolute path based on currentDir.
+ * If it's already absolute then it's left alone.
+ */
+ private[this] def absolutizePath(p: String) = (Path(currentDir.value) resolve Path(p)).normalize.path
+
+ /** All user set settings rewritten with absolute paths based on currentDir */
+ def absolutize() {
userSetSettings foreach {
- case p: OutputSetting => p.outputDirs setSingleOutput AbstractFile.getDirectory(rewrite(p.value))
- case p: PathSetting => p.value = ClassPath.map(p.value, rewrite)
- case p: StringSetting => if (holdsPath(p)) p.value = rewrite(p.value)
+ case p: OutputSetting => p.outputDirs setSingleOutput AbstractFile.getDirectory(absolutizePath(p.value))
+ case p: PathSetting => p.value = ClassPath.map(p.value, absolutizePath)
+ case p: StringSetting => if (holdsPath(p)) p.value = absolutizePath(p.value)
case _ => ()
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
index f8172743b5..0180271925 100644
--- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
+++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
@@ -451,20 +451,45 @@ abstract class LambdaLift extends InfoTransform {
}
case arg => arg
}
- /** Wrap expr argument in new *Ref(..) constructor, but make
- * sure that Try expressions stay at toplevel.
+
+ /** Wrap expr argument in new *Ref(..) constructor. But try/catch
+ * is a problem because a throw will clear the stack and post catch
+ * we would expect the partially-constructed object to be on the stack
+ * for the call to init. So we recursively
+ * search for "leaf" result expressions where we know its safe
+ * to put the new *Ref(..) constructor or, if all else fails, transform
+ * an expr to { val temp=expr; new *Ref(temp) }.
+ * The reason we narrowly look for try/catch in captured var definitions
+ * is because other try/catch expression have already been lifted
+ * see SI-6863
*/
- def refConstr(expr: Tree): Tree = expr match {
+ def refConstr(expr: Tree): Tree = typer.typedPos(expr.pos)(expr match {
+ // very simple expressions can be wrapped in a new *Ref(expr) because they can't have
+ // a try/catch in final expression position.
+ case Ident(_) | Apply(_, _) | Literal(_) | New(_) | Select(_, _) | Throw(_) | Assign(_, _) | ValDef(_, _, _, _) | Return(_) | EmptyTree =>
+ New(sym.tpe, expr)
case Try(block, catches, finalizer) =>
Try(refConstr(block), catches map refConstrCase, finalizer)
+ case Block(stats, expr) =>
+ Block(stats, refConstr(expr))
+ case If(cond, trueBranch, falseBranch) =>
+ If(cond, refConstr(trueBranch), refConstr(falseBranch))
+ case Match(selector, cases) =>
+ Match(selector, cases map refConstrCase)
+ // if we can't figure out what else to do, turn expr into {val temp1 = expr; new *Ref(temp1)} to avoid
+ // any possibility of try/catch in the *Ref constructor. This should be a safe tranformation as a default
+ // though it potentially wastes a variable slot. In particular this case handles LabelDefs.
case _ =>
- New(sym.tpe, expr)
- }
+ debuglog("assigning expr to temp: " + (expr.pos))
+ val tempSym = currentOwner.newValue(unit.freshTermName("temp"), expr.pos) setInfo expr.tpe
+ val tempDef = ValDef(tempSym, expr) setPos expr.pos
+ val tempRef = Ident(tempSym) setPos expr.pos
+ Block(tempDef, New(sym.tpe, tempRef))
+ })
def refConstrCase(cdef: CaseDef): CaseDef =
CaseDef(cdef.pat, cdef.guard, refConstr(cdef.body))
- treeCopy.ValDef(tree, mods, name, tpt1, typer.typedPos(rhs.pos) {
- refConstr(constructorArg)
- })
+
+ treeCopy.ValDef(tree, mods, name, tpt1, refConstr(constructorArg))
} else tree
case Return(Block(stats, value)) =>
Block(stats, treeCopy.Return(tree, value)) setType tree.tpe setPos tree.pos
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index afe853e06e..63908a38e3 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -451,8 +451,6 @@ abstract class UnCurry extends InfoTransform
}
case ValDef(_, _, _, rhs) =>
if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit)
- // a local variable that is mutable and free somewhere later should be lifted
- // as lambda lifting (coming later) will wrap 'rhs' in an Ref object.
if (!sym.owner.isSourceMethod)
withNeedLift(true) { super.transform(tree) }
else
@@ -563,15 +561,22 @@ abstract class UnCurry extends InfoTransform
}
case dd @ DefDef(_, _, _, vparamss0, _, rhs0) =>
- val vparamss1 = vparamss0 match {
- case _ :: Nil => vparamss0
- case _ => vparamss0.flatten :: Nil
- }
+ val (newParamss, newRhs): (List[List[ValDef]], Tree) =
+ if (dependentParamTypeErasure isDependent dd)
+ dependentParamTypeErasure erase dd
+ else {
+ val vparamss1 = vparamss0 match {
+ case _ :: Nil => vparamss0
+ case _ => vparamss0.flatten :: Nil
+ }
+ (vparamss1, rhs0)
+ }
+
val flatdd = copyDefDef(dd)(
- vparamss = vparamss1,
+ vparamss = newParamss,
rhs = nonLocalReturnKeys get dd.symbol match {
- case Some(k) => atPos(rhs0.pos)(nonLocalReturnTry(rhs0, k, dd.symbol))
- case None => rhs0
+ case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(newRhs, k, dd.symbol))
+ case None => newRhs
}
)
addJavaVarargsForwarders(dd, flatdd)
@@ -599,6 +604,104 @@ abstract class UnCurry extends InfoTransform
}
}
+ /**
+ * When we concatenate parameter lists, formal parameter types that were dependent
+ * on prior parameter values will no longer be correctly scoped.
+ *
+ * For example:
+ *
+ * {{{
+ * def foo(a: A)(b: a.B): a.type = {b; b}
+ * // after uncurry
+ * def foo(a: A, b: a/* NOT IN SCOPE! */.B): a.B = {b; b}
+ * }}}
+ *
+ * This violates the principle that each compiler phase should produce trees that
+ * can be retyped (see [[scala.tools.nsc.typechecker.TreeCheckers]]), and causes
+ * a practical problem in `erasure`: it is not able to correctly determine if
+ * such a signature overrides a corresponding signature in a parent. (SI-6443).
+ *
+ * This transformation erases the dependent method types by:
+ * - Widening the formal parameter type to existentially abstract
+ * over the prior parameters (using `packSymbols`)
+ * - Inserting casts in the method body to cast to the original,
+ * precise type.
+ *
+ * For the example above, this results in:
+ *
+ * {{{
+ * def foo(a: A, b: a.B forSome { val a: A }): a.B = { val b$1 = b.asInstanceOf[a.B]; b$1; b$1 }
+ * }}}
+ */
+ private object dependentParamTypeErasure {
+ sealed abstract class ParamTransform {
+ def param: ValDef
+ }
+ final case class Identity(param: ValDef) extends ParamTransform
+ final case class Packed(param: ValDef, tempVal: ValDef) extends ParamTransform
+
+ def isDependent(dd: DefDef): Boolean =
+ enteringUncurry {
+ val methType = dd.symbol.info
+ methType.isDependentMethodType && mexists(methType.paramss)(_.info exists (_.isImmediatelyDependent))
+ }
+
+ /**
+ * @return (newVparamss, newRhs)
+ */
+ def erase(dd: DefDef): (List[List[ValDef]], Tree) = {
+ import dd.{ vparamss, rhs }
+ val vparamSyms = vparamss flatMap (_ map (_.symbol))
+
+ val paramTransforms: List[ParamTransform] =
+ vparamss.flatten.map { p =>
+ val declaredType = p.symbol.info
+ // existentially abstract over value parameters
+ val packedType = typer.packSymbols(vparamSyms, declaredType)
+ if (packedType =:= declaredType) Identity(p)
+ else {
+ // Change the type of the param symbol
+ p.symbol updateInfo packedType
+
+ // Create a new param tree
+ val newParam: ValDef = copyValDef(p)(tpt = TypeTree(packedType))
+
+ // Within the method body, we'll cast the parameter to the originally
+ // declared type and assign this to a synthetic val. Later, we'll patch
+ // the method body to refer to this, rather than the parameter.
+ val tempVal: ValDef = {
+ val tempValName = unit freshTermName (p.name + "$")
+ val newSym = dd.symbol.newTermSymbol(tempValName, p.pos, SYNTHETIC).setInfo(declaredType)
+ atPos(p.pos)(ValDef(newSym, gen.mkAttributedCast(Ident(p.symbol), declaredType)))
+ }
+ Packed(newParam, tempVal)
+ }
+ }
+
+ val allParams = paramTransforms map (_.param)
+ val (packedParams, tempVals) = paramTransforms.collect {
+ case Packed(param, tempVal) => (param, tempVal)
+ }.unzip
+
+ val rhs1 = localTyper.typedPos(rhs.pos) {
+ // Patch the method body to refer to the temp vals
+ val rhsSubstituted = rhs.substituteSymbols(packedParams map (_.symbol), tempVals map (_.symbol))
+ // The new method body: { val p$1 = p.asInstanceOf[<dependent type>]; ...; <rhsSubstituted> }
+ Block(tempVals, rhsSubstituted)
+ }
+
+ // update the type of the method after uncurry.
+ dd.symbol updateInfo {
+ val GenPolyType(tparams, tp) = dd.symbol.info
+ logResult("erased dependent param types for ${dd.symbol.info}") {
+ GenPolyType(tparams, MethodType(allParams map (_.symbol), tp.finalResultType))
+ }
+ }
+ (allParams :: Nil, rhs1)
+ }
+ }
+
+
/* Analyzes repeated params if method is annotated as `varargs`.
* If the repeated params exist, it saves them into the `repeatedParams` map,
* which is used later.
diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
index 1c1e9c10fb..a112516b59 100644
--- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
@@ -116,7 +116,8 @@ abstract class TreeCheckers extends Analyzer {
try p.source.path + ":" + p.line
catch { case _: UnsupportedOperationException => p.toString }
- def errorFn(msg: Any): Unit = println("[check: %s] %s".format(phase.prev, msg))
+ private var hasError: Boolean = false
+ def errorFn(msg: Any): Unit = {hasError = true; println("[check: %s] %s".format(phase.prev, msg))}
def errorFn(pos: Position, msg: Any): Unit = errorFn(posstr(pos) + ": " + msg)
def informFn(msg: Any) {
if (settings.verbose.value || settings.debug.value)
@@ -143,6 +144,7 @@ abstract class TreeCheckers extends Analyzer {
}
def runWithUnit[T](unit: CompilationUnit)(body: => Unit): Unit = {
+ hasError = false
val unit0 = currentUnit
currentRun.currentUnit = unit
body
@@ -161,6 +163,7 @@ abstract class TreeCheckers extends Analyzer {
checker.precheck.traverse(unit.body)
checker.typed(unit.body)
checker.postcheck.traverse(unit.body)
+ if (hasError) unit.warning(NoPosition, "TreeCheckers detected non-compliant trees in " + unit)
}
}
@@ -202,8 +205,11 @@ abstract class TreeCheckers extends Analyzer {
case _ => ()
}
- object precheck extends Traverser {
+ object precheck extends TreeStackTraverser {
override def traverse(tree: Tree) {
+ checkSymbolRefsRespectScope(tree)
+ checkReturnReferencesDirectlyEnclosingDef(tree)
+
val sym = tree.symbol
def accessed = sym.accessed
def fail(msg: String) = errorFn(tree.pos, msg + classstr(tree) + " / " + tree)
@@ -274,6 +280,41 @@ abstract class TreeCheckers extends Analyzer {
}
super.traverse(tree)
}
+
+ private def checkSymbolRefsRespectScope(tree: Tree) {
+ def symbolOf(t: Tree): Symbol = Option(tree.symbol).getOrElse(NoSymbol)
+ def definedSymbolOf(t: Tree): Symbol = if (t.isDef) symbolOf(t) else NoSymbol
+ val info = Option(symbolOf(tree).info).getOrElse(NoType)
+ val referencedSymbols: List[Symbol] = {
+ val directRef = tree match {
+ case _: RefTree => symbolOf(tree).toOption
+ case _ => None
+ }
+ def referencedSyms(tp: Type) = (tp collect {
+ case TypeRef(_, sym, _) => sym
+ }).toList
+ val indirectRefs = referencedSyms(info)
+ (indirectRefs ++ directRef).distinct
+ }
+ for {
+ sym <- referencedSymbols
+ if (sym.isTypeParameter || sym.isLocal) && !(tree.symbol hasTransOwner sym.owner)
+ } errorFn(s"The symbol, tpe or info of tree `(${tree}) : ${info}` refers to a out-of-scope symbol, ${sym.fullLocationString}. tree.symbol.ownerChain: ${tree.symbol.ownerChain.mkString(", ")}")
+ }
+
+ private def checkReturnReferencesDirectlyEnclosingDef(tree: Tree) {
+ tree match {
+ case _: Return =>
+ path.collectFirst {
+ case dd: DefDef => dd
+ } match {
+ case None => errorFn(s"Return node ($tree) must be enclosed in a DefDef")
+ case Some(dd) =>
+ if (tree.symbol != dd.symbol) errorFn(s"Return symbol (${tree.symbol}} does not reference directly enclosing DefDef (${dd.symbol})")
+ }
+ case _ =>
+ }
+ }
}
object postcheck extends Traverser {
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 8fbc143fbf..994339a028 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -1396,7 +1396,7 @@ trait Typers extends Adaptations with Tags {
case DefDef(_, name, _, _, _, rhs) =>
if (stat.symbol.isAuxiliaryConstructor)
notAllowed("secondary constructor")
- else if (isValueClass && (name == nme.equals_ || name == nme.hashCode_))
+ else if (isValueClass && (name == nme.equals_ || name == nme.hashCode_) && !stat.symbol.isSynthetic)
notAllowed(s"redefinition of $name method. See SIP-15, criterion 4.")
else if (stat.symbol != null && stat.symbol.isParamAccessor)
notAllowed("additional parameter")
diff --git a/src/library/scala/concurrent/BatchingExecutor.scala b/src/library/scala/concurrent/BatchingExecutor.scala
new file mode 100644
index 0000000000..a0d7aaea47
--- /dev/null
+++ b/src/library/scala/concurrent/BatchingExecutor.scala
@@ -0,0 +1,117 @@
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+package scala.concurrent
+
+import java.util.concurrent.Executor
+import scala.annotation.tailrec
+
+/**
+ * Mixin trait for an Executor
+ * which groups multiple nested `Runnable.run()` calls
+ * into a single Runnable passed to the original
+ * Executor. This can be a useful optimization
+ * because it bypasses the original context's task
+ * queue and keeps related (nested) code on a single
+ * thread which may improve CPU affinity. However,
+ * if tasks passed to the Executor are blocking
+ * or expensive, this optimization can prevent work-stealing
+ * and make performance worse. Also, some ExecutionContext
+ * may be fast enough natively that this optimization just
+ * adds overhead.
+ * The default ExecutionContext.global is already batching
+ * or fast enough not to benefit from it; while
+ * `fromExecutor` and `fromExecutorService` do NOT add
+ * this optimization since they don't know whether the underlying
+ * executor will benefit from it.
+ * A batching executor can create deadlocks if code does
+ * not use `scala.concurrent.blocking` when it should,
+ * because tasks created within other tasks will block
+ * on the outer task completing.
+ * This executor may run tasks in any order, including LIFO order.
+ * There are no ordering guarantees.
+ *
+ * WARNING: The underlying Executor's execute-method must not execute the submitted Runnable
+ * in the calling thread synchronously. It must enqueue/handoff the Runnable.
+ */
+private[concurrent] trait BatchingExecutor extends Executor {
+
+ // invariant: if "_tasksLocal.get ne null" then we are inside BatchingRunnable.run; if it is null, we are outside
+ private val _tasksLocal = new ThreadLocal[List[Runnable]]()
+
+ private class Batch(val initial: List[Runnable]) extends Runnable with BlockContext {
+ private var parentBlockContext: BlockContext = _
+ // this method runs in the delegate ExecutionContext's thread
+ override def run(): Unit = {
+ require(_tasksLocal.get eq null)
+
+ val prevBlockContext = BlockContext.current
+ BlockContext.withBlockContext(this) {
+ try {
+ parentBlockContext = prevBlockContext
+
+ @tailrec def processBatch(batch: List[Runnable]): Unit = batch match {
+ case Nil => ()
+ case head :: tail =>
+ _tasksLocal set tail
+ try {
+ head.run()
+ } catch {
+ case t: Throwable =>
+ // if one task throws, move the
+ // remaining tasks to another thread
+ // so we can throw the exception
+ // up to the invoking executor
+ val remaining = _tasksLocal.get
+ _tasksLocal set Nil
+ unbatchedExecute(new Batch(remaining)) //TODO what if this submission fails?
+ throw t // rethrow
+ }
+ processBatch(_tasksLocal.get) // since head.run() can add entries, always do _tasksLocal.get here
+ }
+
+ processBatch(initial)
+ } finally {
+ _tasksLocal.remove()
+ parentBlockContext = null
+ }
+ }
+ }
+
+ override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = {
+ // if we know there will be blocking, we don't want to keep tasks queued up because it could deadlock.
+ {
+ val tasks = _tasksLocal.get
+ _tasksLocal set Nil
+ if ((tasks ne null) && tasks.nonEmpty)
+ unbatchedExecute(new Batch(tasks))
+ }
+
+ // now delegate the blocking to the previous BC
+ require(parentBlockContext ne null)
+ parentBlockContext.blockOn(thunk)
+ }
+ }
+
+ protected def unbatchedExecute(r: Runnable): Unit
+
+ override def execute(runnable: Runnable): Unit = {
+ if (batchable(runnable)) { // If we can batch the runnable
+ _tasksLocal.get match {
+ case null => unbatchedExecute(new Batch(List(runnable))) // If we aren't in batching mode yet, enqueue batch
+ case some => _tasksLocal.set(runnable :: some) // If we are already in batching mode, add to batch
+ }
+ } else unbatchedExecute(runnable) // If not batchable, just delegate to underlying
+ }
+
+ /** Override this to define which runnables will be batched. */
+ def batchable(runnable: Runnable): Boolean = runnable match {
+ case _: OnCompleteRunnable => true
+ case _ => false
+ }
+}
diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala
index 4b9e74708d..36f3be341f 100644
--- a/src/library/scala/concurrent/Future.scala
+++ b/src/library/scala/concurrent/Future.scala
@@ -675,9 +675,9 @@ object Future {
// by just not ever using it itself. scala.concurrent
// doesn't need to create defaultExecutionContext as
// a side effect.
- private[concurrent] object InternalCallbackExecutor extends ExecutionContext {
- override def execute(runnable: Runnable): Unit =
- runnable.run()
+ private[concurrent] object InternalCallbackExecutor extends ExecutionContext with BatchingExecutor {
+ override protected def unbatchedExecute(r: Runnable): Unit =
+ r.run()
override def reportFailure(t: Throwable): Unit =
throw new IllegalStateException("problem in scala.concurrent internal callback", t)
}
diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala
index 0969d9e3fa..745a665062 100644
--- a/src/reflect/scala/reflect/internal/Symbols.scala
+++ b/src/reflect/scala/reflect/internal/Symbols.scala
@@ -1690,6 +1690,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
@inline final def map(f: Symbol => Symbol): Symbol = if (this eq NoSymbol) this else f(this)
+ final def toOption: Option[Symbol] = if (exists) Some(this) else None
+
// ------ cloneing -------------------------------------------------------------------
/** A clone of this symbol. */
diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala
index 3c2249bfb5..9b185c1c2d 100644
--- a/src/reflect/scala/reflect/internal/Trees.scala
+++ b/src/reflect/scala/reflect/internal/Trees.scala
@@ -1469,6 +1469,22 @@ trait Trees extends api.Trees { self: SymbolTable =>
if (tree.hasSymbolField) {
subst(from, to)
tree match {
+ case _: DefTree =>
+ val newInfo = symSubst(tree.symbol.info)
+ if (!(newInfo =:= tree.symbol.info)) {
+ debuglog(sm"""
+ |TreeSymSubstituter: updated info of symbol ${tree.symbol}
+ | Old: ${showRaw(tree.symbol.info, printTypes = true, printIds = true)}
+ | New: ${showRaw(newInfo, printTypes = true, printIds = true)}""")
+ tree.symbol updateInfo newInfo
+ }
+ case _ =>
+ // no special handling is required for Function or Import nodes here.
+ // as they don't have interesting infos attached to their symbols.
+ // Subsitution of the referenced symbol of Return nodes is handled
+ // in .ChangeOwnerTraverser
+ }
+ tree match {
case Ident(name0) if tree.symbol != NoSymbol =>
treeCopy.Ident(tree, tree.symbol.name)
case Select(qual, name0) if tree.symbol != NoSymbol =>
@@ -1526,6 +1542,14 @@ trait Trees extends api.Trees { self: SymbolTable =>
t1
}
}
+ trait TreeStackTraverser extends Traverser {
+ import collection.mutable
+ val path: mutable.Stack[Tree] = mutable.Stack()
+ abstract override def traverse(t: Tree) = {
+ path push t
+ try super.traverse(t) finally path.pop()
+ }
+ }
def duplicateAndKeepPositions(tree: Tree) = new Duplicator(focusPositions = false) transform tree
diff --git a/src/reflect/scala/reflect/macros/Attachments.scala b/src/reflect/scala/reflect/macros/Attachments.scala
index a77cebf415..eeb87fafcc 100644
--- a/src/reflect/scala/reflect/macros/Attachments.scala
+++ b/src/reflect/scala/reflect/macros/Attachments.scala
@@ -44,17 +44,19 @@ abstract class Attachments { self =>
* Replaces an existing payload of the same type, if exists.
*/
def update[T: ClassTag](attachment: T): Attachments { type Pos = self.Pos } =
- new NonemptyAttachments(this.pos, remove[T].all + attachment)
+ new NonemptyAttachments[Pos](this.pos, remove[T].all + attachment)
/** Creates a copy of this attachment with the payload of the given class type `T` removed. */
def remove[T: ClassTag]: Attachments { type Pos = self.Pos } = {
val newAll = all filterNot matchesTag[T]
if (newAll.isEmpty) pos.asInstanceOf[Attachments { type Pos = self.Pos }]
- else new NonemptyAttachments(this.pos, newAll)
+ else new NonemptyAttachments[Pos](this.pos, newAll)
}
+}
- private class NonemptyAttachments(override val pos: Pos, override val all: Set[Any]) extends Attachments {
- type Pos = self.Pos
- def withPos(newPos: Pos) = new NonemptyAttachments(newPos, all)
- }
+// SI-7018: This used to be an inner class of `Attachments`, but that led to a memory leak in the
+// IDE via $outer pointers.
+private final class NonemptyAttachments[P >: Null](override val pos: P, override val all: Set[Any]) extends Attachments {
+ type Pos = P
+ def withPos(newPos: Pos) = new NonemptyAttachments(newPos, all)
}
diff --git a/test/files/disabled/run/t4602.scala b/test/files/disabled/run/t4602.scala
new file mode 100644
index 0000000000..73ba231ccf
--- /dev/null
+++ b/test/files/disabled/run/t4602.scala
@@ -0,0 +1,57 @@
+import java.io.{File, FileOutputStream, BufferedOutputStream, FileWriter, ByteArrayOutputStream, PrintStream}
+import tools.nsc.{CompileClient, CompileServer}
+import java.util.concurrent.{CountDownLatch, TimeUnit}
+
+object Test extends App {
+ val startupLatch = new CountDownLatch(1)
+ // we have to explicitly launch our server because when the client launches a server it uses
+ // the "scala" shell command meaning whatever version of scala (and whatever version of libraries)
+ // happens to be in the path gets used
+ val t = new Thread(new Runnable {
+ def run() = {
+ CompileServer.execute(() => startupLatch.countDown(), Array[String]())
+ }
+ })
+ t setDaemon true
+ t.start()
+ if (!startupLatch.await(2, TimeUnit.MINUTES))
+ sys error "Timeout waiting for server to start"
+
+ val baos = new ByteArrayOutputStream()
+ val ps = new PrintStream(baos)
+
+ val outdir = scala.reflect.io.Directory(sys.props("partest.output"))
+
+ val dirNameAndPath = (1 to 2).toList map {number =>
+ val name = s"Hello${number}"
+ val dir = outdir / number.toString
+ (dir, name, dir / s"${name}.scala")
+ }
+
+ dirNameAndPath foreach {case (dir, name, path) =>
+ dir.createDirectory()
+ val file = path.jfile
+ val out = new FileWriter(file)
+ try
+ out.write(s"object ${name}\n")
+ finally
+ out.close
+ }
+
+ val success = (scala.Console withOut ps) {
+ dirNameAndPath foreach {case (path, name, _) =>
+ CompileClient.process(Array("-verbose", "-current-dir", path.toString, s"${name}.scala"))
+ }
+
+ CompileClient.process(Array("-shutdown"))
+ }
+
+ // now make sure we got success and the correct normalized paths
+ val msg = baos.toString()
+
+ assert(success, s"got a failure. Full results were: \n${msg}")
+ dirNameAndPath foreach {case (_, _, path) =>
+ val expected = s"Input files after normalizing paths: ${path}"
+ assert(msg contains expected, s"could not find '${expected}' in output. Full results were: \n${msg}")
+ }
+}
diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala
index b529bca38a..b2b4183564 100644
--- a/test/files/jvm/scala-concurrent-tck.scala
+++ b/test/files/jvm/scala-concurrent-tck.scala
@@ -134,6 +134,12 @@ trait FutureCallbacks extends TestBase {
assert(false)
}
}
+
+ def testThatNestedCallbacksDoNotYieldStackOverflow(): Unit = {
+ val promise = Promise[Int]
+ (0 to 10000).map(Future(_)).foldLeft(promise.future)((f1, f2) => f2.flatMap(i => f1))
+ promise.success(-1)
+ }
testOnSuccess()
testOnSuccessWhenCompleted()
@@ -143,6 +149,7 @@ trait FutureCallbacks extends TestBase {
// testOnFailureWhenSpecialThrowable(6, new scala.util.control.ControlThrowable { })
//TODO: this test is currently problematic, because NonFatal does not match InterruptedException
//testOnFailureWhenSpecialThrowable(7, new InterruptedException)
+ testThatNestedCallbacksDoNotYieldStackOverflow()
testOnFailureWhenTimeoutException()
}
diff --git a/test/files/neg/t6443c.check b/test/files/neg/t6443c.check
new file mode 100644
index 0000000000..7cf8d23f4b
--- /dev/null
+++ b/test/files/neg/t6443c.check
@@ -0,0 +1,7 @@
+t6443c.scala:16: error: double definition:
+method foo:(d: B.D)(a: Any)(d2: d.type)Unit and
+method foo:(d: B.D)(a: Any, d2: d.type)Unit at line 11
+have same type after erasure: (d: B.D, a: Object, d2: B.D)Unit
+ def foo(d: D)(a: Any)(d2: d.type): Unit = ()
+ ^
+one error found
diff --git a/test/files/neg/t6443c.scala b/test/files/neg/t6443c.scala
new file mode 100644
index 0000000000..817224e043
--- /dev/null
+++ b/test/files/neg/t6443c.scala
@@ -0,0 +1,21 @@
+trait A {
+ type D >: Null <: C
+ def foo(d: D)(a: Any, d2: d.type): Unit
+ trait C {
+ def bar: Unit = foo(null)(null, null)
+ }
+}
+object B extends A {
+ class D extends C
+
+ def foo(d: D)(a: Any, d2: d.type): Unit = () // Bridge method required here!
+
+ // No bridge method should be added, but we'll be happy enough if
+ // the "same type after erasure" error kicks in before the duplicated
+ // bridge causes a problem.
+ def foo(d: D)(a: Any)(d2: d.type): Unit = ()
+}
+
+object Test extends App {
+ new B.D().bar
+}
diff --git a/test/files/pos/t6891.flags b/test/files/pos/t6891.flags
new file mode 100644
index 0000000000..fe048006aa
--- /dev/null
+++ b/test/files/pos/t6891.flags
@@ -0,0 +1 @@
+-Ycheck:extmethods -Xfatal-warnings \ No newline at end of file
diff --git a/test/files/pos/t6891.scala b/test/files/pos/t6891.scala
new file mode 100644
index 0000000000..bed2d0d777
--- /dev/null
+++ b/test/files/pos/t6891.scala
@@ -0,0 +1,26 @@
+object O {
+ implicit class Foo[A](val value: String) extends AnyVal {
+ def bippy() = {
+ @annotation.tailrec def loop(x: A): Unit = loop(x)
+ ()
+ }
+
+ def boppy() = {
+ @annotation.tailrec def loop(x: value.type): Unit = loop(x)
+ ()
+ }
+
+ def beppy[C](c: => C) = {
+ () => c
+ @annotation.tailrec def loop(x: value.type): Unit = loop(x)
+ () => c
+ ()
+ }
+ }
+ // uncaught exception during compilation: Types$TypeError("type mismatch;
+ // found : A(in method bippy$extension)
+ // required: A(in class Foo)") @ scala.tools.nsc.typechecker.Contexts$Context.issueCommon(Contexts.scala:396)
+ // error: scala.reflect.internal.Types$TypeError: type mismatch;
+ // found : A(in method bippy$extension)
+ // required: A(in class Foo)
+}
diff --git a/test/files/run/t6135.scala b/test/files/run/t6135.scala
new file mode 100644
index 0000000000..c0f8f3fd1d
--- /dev/null
+++ b/test/files/run/t6135.scala
@@ -0,0 +1,13 @@
+object Test extends App {
+ class A { class V }
+
+ abstract class B[S] {
+ def foo(t: S, a: A)(v: a.V)
+ }
+
+ val b1 = new B[String] {
+ def foo(t: String, a: A)(v: a.V) = () // Bridge method required here!
+ }
+
+ b1.foo("", null)(null)
+}
diff --git a/test/files/run/t6443-by-name.check b/test/files/run/t6443-by-name.check
new file mode 100644
index 0000000000..6f98fa4a28
--- /dev/null
+++ b/test/files/run/t6443-by-name.check
@@ -0,0 +1,3 @@
+1
+foo
+foo
diff --git a/test/files/run/t6443-by-name.scala b/test/files/run/t6443-by-name.scala
new file mode 100644
index 0000000000..bfd9bf9791
--- /dev/null
+++ b/test/files/run/t6443-by-name.scala
@@ -0,0 +1,18 @@
+object Test {
+
+ def main(args: Array[String]) {
+ def foo = {println("foo"); 0}
+ lazyDep(X)(foo)
+ }
+
+ trait T {
+ type U
+ }
+ object X extends T { type U = Int }
+
+ def lazyDep(t: T)(u: => t.U) {
+ println("1")
+ u
+ u
+ }
+}
diff --git a/test/files/run/t6443-varargs.check b/test/files/run/t6443-varargs.check
new file mode 100644
index 0000000000..257cc5642c
--- /dev/null
+++ b/test/files/run/t6443-varargs.check
@@ -0,0 +1 @@
+foo
diff --git a/test/files/run/t6443-varargs.scala b/test/files/run/t6443-varargs.scala
new file mode 100644
index 0000000000..9cbae3e99c
--- /dev/null
+++ b/test/files/run/t6443-varargs.scala
@@ -0,0 +1,16 @@
+object Test {
+
+ def main(args: Array[String]) {
+ def foo = {println("foo"); 0}
+ lazyDep(X)(foo)
+ }
+
+ trait T {
+ type U
+ }
+ object X extends T { type U = Int }
+
+ def lazyDep(t: T)(us: t.U*) {
+ List(us: _*)
+ }
+}
diff --git a/test/files/run/t6443.scala b/test/files/run/t6443.scala
new file mode 100644
index 0000000000..67fe2cab22
--- /dev/null
+++ b/test/files/run/t6443.scala
@@ -0,0 +1,15 @@
+class Base
+class Derived extends Base
+
+trait A {
+ def foo(d: String)(d2: d.type): Base
+ val s = ""
+ def bar: Unit = foo(s)(s)
+}
+object B extends A {
+ def foo(d: String)(d2: d.type): D forSome { type D <: S; type S <: Derived } = {d2.isEmpty; null} // Bridge method required here!
+}
+
+object Test extends App {
+ B.bar
+}
diff --git a/test/files/run/t6443b.scala b/test/files/run/t6443b.scala
new file mode 100644
index 0000000000..9320b1dcfe
--- /dev/null
+++ b/test/files/run/t6443b.scala
@@ -0,0 +1,16 @@
+trait A {
+ type D >: Null <: C
+ def foo(d: D)(d2: d.type): Unit
+ trait C {
+ def bar: Unit = foo(null)(null)
+ }
+}
+object B extends A {
+ class D extends C
+
+ def foo(d: D)(d2: d.type): Unit = () // Bridge method required here!
+}
+
+object Test extends App {
+ new B.D().bar
+}
diff --git a/test/files/run/t6863.scala b/test/files/run/t6863.scala
new file mode 100644
index 0000000000..d77adb6af4
--- /dev/null
+++ b/test/files/run/t6863.scala
@@ -0,0 +1,114 @@
+/** Make sure that when a variable is captured its initialization expression is handled properly */
+object Test {
+ def lazyVal() = {
+ // internally lazy vals become vars which are initialized with "_", so they need to be tested just like vars do
+ lazy val x = "42"
+ assert({ () => x }.apply == "42")
+ }
+ def ident() = {
+ val y = "42"
+ var x = y
+ assert({ () => x }.apply == "42")
+ }
+ def apply() = {
+ def y(x : Int) = x.toString
+ var x = y(42)
+ assert({ () => x }.apply == "42")
+ }
+ def literal() = {
+ var x = "42"
+ assert({ () => x }.apply == "42")
+ }
+ def `new`() = {
+ var x = new String("42")
+ assert({ () => x }.apply == "42")
+ }
+ def select() = {
+ object Foo{val bar = "42"}
+ var x = Foo.bar
+ assert({ () => x }.apply == "42")
+ }
+ def `throw`() = {
+ var x = if (true) "42" else throw new Exception("42")
+ assert({ () => x }.apply == "42")
+ }
+ def assign() = {
+ var y = 1
+ var x = y = 42
+ assert({ () => x}.apply == ())
+ }
+ def valDef() = {
+ var x = {val y = 42}
+ assert({ () => x}.apply == ())
+ }
+ def `return`(): String = {
+ var x = if (true) return "42" else ()
+ assert({ () => x}.apply == ())
+ "42"
+ }
+ def tryFinally() = {
+ var x = try { "42" } finally ()
+ assert({ () => x }.apply == "42")
+ }
+ def tryCatch() = {
+ var x = try { "42" } catch { case _ => "43" }
+ assert({ () => x }.apply == "42")
+ }
+ def `if`() = {
+ var x = if (true) ()
+ assert({ () => x }.apply == ())
+ }
+ def ifElse() = {
+ var x = if(true) "42" else "43"
+ assert({ () => x }.apply == "42")
+ }
+ def matchCase() = {
+ var x = 100 match {
+ case 100 => "42"
+ case _ => "43"
+ }
+ assert({ () => x }.apply == "42")
+ }
+ def block() = {
+ var x = {
+ val y = 42
+ "42"
+ }
+ assert({ () => x }.apply == "42")
+ }
+ def labelDef() = {
+ var x = 100 match {
+ case 100 => try "42" finally ()
+ }
+ assert({ () => x }.apply == "42")
+ }
+ def nested() = {
+ var x = {
+ val y = 42
+ if(true) try "42" catch {case _ => "43"}
+ else "44"
+ }
+ assert({ () => x }.apply == "42")
+ }
+ def main(args: Array[String]) {
+ lazyVal()
+ ident()
+ apply()
+ literal()
+ `new`()
+ select()
+ `throw`()
+ assign()
+ valDef()
+ `return`()
+ tryFinally()
+ tryCatch()
+ ifElse()
+ `if`()
+ matchCase()
+ block()
+ labelDef()
+ nested()
+ }
+}
+
diff --git a/test/partest b/test/partest
index 2bd8ce94f3..9b0ab02fdc 100755
--- a/test/partest
+++ b/test/partest
@@ -82,7 +82,9 @@ fi
# last arg wins, so if JAVA_OPTS already contains -Xmx or -Xms the
# supplied argument will be used.
-JAVA_OPTS="-Xmx1024M -Xms64M $JAVA_OPTS"
+# At this writing it is reported test/partest --all requires 108m permgen.
+JAVA_OPTS="-Xmx1024M -Xms64M -XX:MaxPermSize=128M $JAVA_OPTS"
+
# the ant task doesn't supply any options by default,
# so don't to that here either -- note that you may want to pass -optimise
# to mimic what happens during nightlies