Skip to content

Commit ecdaecc

Browse files
committed
-Vcyclic for CyclicRef trace locked symbols
1 parent 11af0ed commit ecdaecc

File tree

11 files changed

+128
-56
lines changed

11 files changed

+128
-56
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
9090

9191
override def settings = currentSettings
9292

93+
override def isSymbolLockTracingEnabled = settings.cyclic.value
94+
9395
private[this] var currentReporter: FilteringReporter = null
9496
locally { reporter = reporter0 }
9597

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ trait ScalaSettings extends StandardScalaSettings with Warnings { _: MutableSett
563563
*/
564564
val Vhelp = BooleanSetting("-V", "Print a synopsis of verbose options.")
565565
val browse = PhasesSetting("-Vbrowse", "Browse the abstract syntax tree after") withAbbreviation "-Ybrowse"
566+
val cyclic = BooleanSetting("-Vcyclic", "Debug cyclic reference error.")
566567
val debug = BooleanSetting("-Vdebug", "Increase the quantity of debugging output.") withAbbreviation "-Ydebug" withPostSetHook (s => if (s.value) StatisticsStatics.enableDebugAndDeoptimize())
567568
val YdebugTasty = BooleanSetting("-Vdebug-tasty", "Increase the quantity of debugging output when unpickling tasty.") withAbbreviation "-Ydebug-tasty"
568569
val VdebugTypeError = BooleanSetting("-Vdebug-type-error", "Print the stack trace when any error is caught.") withAbbreviation "-Ydebug-type-error"

src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,7 @@ trait ContextErrors extends splain.SplainErrors {
13401340

13411341
def TypeSigError(tree: Tree, ex: TypeError) = {
13421342
ex match {
1343-
case CyclicReference(_, _) if tree.symbol.isTermMacro =>
1343+
case CyclicReference(_, _, _) if tree.symbol.isTermMacro =>
13441344
// say, we have a macro def `foo` and its macro impl `impl`
13451345
// if impl: 1) omits return type, 2) has anything implicit in its body, 3) sees foo
13461346
//
@@ -1353,8 +1353,8 @@ trait ContextErrors extends splain.SplainErrors {
13531353
// hence we (together with reportTypeError in TypeDiagnostics) make sure that this CyclicReference
13541354
// evades all the handlers on its way and successfully reaches `isCyclicOrErroneous` in Implicits
13551355
throw ex
1356-
case CyclicReference(sym, info: TypeCompleter) =>
1357-
issueNormalTypeError(tree, typer.cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage)
1356+
case CyclicReference(sym, info: TypeCompleter, trace) =>
1357+
issueNormalTypeError(tree, typer.cyclicReferenceMessage(sym, info.tree, trace, tree.pos).getOrElse(ex.getMessage))
13581358
case _ =>
13591359
contextNamerErrorGen.issue(TypeErrorWithUnderlyingTree(tree, ex))
13601360
}

src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -840,19 +840,29 @@ trait TypeDiagnostics extends splain.SplainDiagnostics {
840840
def permanentlyHiddenWarning(pos: Position, hidden: Name, defn: Symbol) =
841841
context.warning(pos, "imported `%s` is permanently hidden by definition of %s".format(hidden, defn.fullLocationString), WarningCategory.OtherShadowing)
842842

843-
private def symWasOverloaded(sym: Symbol) = sym.owner.isClass && sym.owner.info.member(sym.name).isOverloaded
844-
private def cyclicAdjective(sym: Symbol) = if (symWasOverloaded(sym)) "overloaded" else "recursive"
845-
846843
/** Returns Some(msg) if the given tree is untyped apparently due
847844
* to a cyclic reference, and None otherwise.
848845
*/
849-
def cyclicReferenceMessage(sym: Symbol, tree: Tree) = condOpt(tree) {
850-
case ValDef(_, _, TypeTree(), _) => s"recursive $sym needs type"
851-
case DefDef(_, _, _, _, TypeTree(), _) => s"${cyclicAdjective(sym)} $sym needs result type"
852-
case Import(expr, selectors) =>
853-
"""encountered unrecoverable cycle resolving import.
854-
|Note: this is often due in part to a class depending on a definition nested within its companion.
855-
|If applicable, you may wish to try moving some members into another object.""".stripMargin
846+
def cyclicReferenceMessage(sym: Symbol, tree: Tree, trace: Array[Symbol], pos: Position) = {
847+
def symWasOverloaded(sym: Symbol) = sym.owner.isClass && sym.owner.info.member(sym.name).isOverloaded
848+
def cyclicAdjective(sym: Symbol) = if (symWasOverloaded(sym)) "overloaded" else "recursive"
849+
850+
val badsym = if (!sym.isSynthetic) sym else {
851+
val organics = trace.filter(!_.isSynthetic)
852+
if (organics.length == 0) sym
853+
else if (organics.length == 1) organics(0)
854+
else organics.find(_.pos.focus == pos.focus).getOrElse(organics(0))
855+
}
856+
def help = if (!badsym.isSynthetic || settings.cyclic.value) "" else
857+
s"; $badsym is synthetic; use -Vcyclic to find which definition needs an explicit type"
858+
condOpt(tree) {
859+
case ValDef(_, _, TypeTree(), _) => s"recursive $badsym needs type$help"
860+
case DefDef(_, _, _, _, TypeTree(), _) => s"${cyclicAdjective(badsym)} $badsym needs result type$help"
861+
case Import(_, _) =>
862+
sm"""encountered unrecoverable cycle resolving import.
863+
|Note: this is often due in part to a class depending on a definition nested within its companion.
864+
|If applicable, you may wish to try moving some members into another object."""
865+
}
856866
}
857867

858868
// warn about class/method/type-members' type parameters that shadow types already in scope
@@ -887,20 +897,16 @@ trait TypeDiagnostics extends splain.SplainDiagnostics {
887897
if (settings.isDebug) ex.printStackTrace()
888898

889899
ex match {
890-
case CyclicReference(sym, info: TypeCompleter) =>
891-
if (context0.owner.isTermMacro) {
892-
// see comments to TypeSigError for an explanation of this special case
893-
throw ex
894-
} else {
895-
val pos = info.tree match {
896-
case Import(expr, _) => expr.pos
897-
case _ => ex.pos
898-
}
899-
context0.error(pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage())
900-
901-
if (sym == ObjectClass)
902-
throw new FatalError("cannot redefine root "+sym)
900+
// see comments to TypeSigError for an explanation of this special case
901+
case _: CyclicReference if context0.owner.isTermMacro => throw ex
902+
case CyclicReference(sym, info: TypeCompleter, trace) =>
903+
val pos = info.tree match {
904+
case Import(expr, _) => expr.pos
905+
case _ => ex.pos
903906
}
907+
context0.error(pos, cyclicReferenceMessage(sym, info.tree, trace, pos).getOrElse(ex.getMessage))
908+
909+
if (sym == ObjectClass) throw new FatalError(s"cannot redefine root $sym")
904910
case _ =>
905911
context0.error(ex.pos, ex.msg)
906912
}

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6306,7 +6306,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
63066306
case ex: TypeError =>
63076307
tree.clearType()
63086308
// The only problematic case are (recoverable) cyclic reference errors which can pop up almost anywhere.
6309-
typingStack.printTyping(tree, "caught %s: while typing %s".format(ex, tree)) //DEBUG
6309+
typingStack.printTyping(tree, s"caught $ex: while typing $tree")
63106310
reportTypeError(context, tree.pos, ex)
63116311
setError(tree)
63126312
case ex: Exception =>

src/reflect/scala/reflect/internal/SymbolTable.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ abstract class SymbolTable extends macros.Universe
9595

9696
def settings: MutableSettings
9797

98+
def isSymbolLockTracingEnabled: Boolean = isDeveloper
99+
98100
/** Override with final implementation for inlining. */
99101
def debuglog(msg: => String): Unit = if (settings.isDebug) log(msg)
100102

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ package scala
1818
package reflect
1919
package internal
2020

21-
import scala.collection.immutable
22-
import scala.collection.mutable.ListBuffer
23-
import util.{ ReusableInstance, Statistics, shortClassOfInstance }
24-
import Flags._
2521
import scala.annotation.tailrec
22+
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
2623
import scala.reflect.io.{AbstractFile, NoAbstractFile}
24+
25+
import util.{ReusableInstance, Statistics, shortClassOfInstance}
26+
import Flags._
2727
import Variance._
2828

2929
trait Symbols extends api.Symbols { self: SymbolTable =>
@@ -36,14 +36,16 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
3636
protected def nextId() = { ids += 1; ids }
3737

3838
/** Used to keep track of the recursion depth on locked symbols */
39-
private[this] var _recursionTable = immutable.Map.empty[Symbol, Int]
39+
private[this] var _recursionTable = Map.empty[Symbol, Int]
4040
def recursionTable = _recursionTable
41-
def recursionTable_=(value: immutable.Map[Symbol, Int]) = _recursionTable = value
41+
def recursionTable_=(value: Map[Symbol, Int]) = _recursionTable = value
4242

4343
private[this] var _lockedCount = 0
4444
def lockedCount = this._lockedCount
4545
def lockedCount_=(i: Int) = _lockedCount = i
4646

47+
private[this] val _lockingTrace = ArrayBuffer.empty[Symbol]
48+
private[this] val lockTracing: Boolean = self.isSymbolLockTracingEnabled
4749

4850
@deprecated("Global existential IDs no longer used", "2.12.1")
4951
private[this] var existentialIds = 0
@@ -553,19 +555,23 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
553555
// True if the symbol is unlocked.
554556
// True if the symbol is locked but still below the allowed recursion depth.
555557
// False otherwise
556-
private[scala] def lockOK: Boolean = {
557-
((_rawflags & LOCKED) == 0L) ||
558-
((settings.Yrecursion.value != 0) &&
559-
(recursionTable get this match {
560-
case Some(n) => (n <= settings.Yrecursion.value)
561-
case None => true }))
562-
}
558+
private[scala] def lockOK: Boolean = (
559+
(_rawflags & LOCKED) == 0L || {
560+
val limit = settings.Yrecursion.value
561+
limit != 0 && (
562+
recursionTable.get(this) match {
563+
case Some(n) => n <= limit
564+
case None => true
565+
})
566+
}
567+
)
563568

564569
// Lock a symbol, using the handler if the recursion depth becomes too great.
565570
private[scala] def lock(handler: => Unit): Boolean = {
571+
if (lockTracing) _lockingTrace.addOne(this)
566572
if ((_rawflags & LOCKED) != 0L) {
567573
if (settings.Yrecursion.value != 0) {
568-
recursionTable get this match {
574+
recursionTable.get(this) match {
569575
case Some(n) =>
570576
if (n > settings.Yrecursion.value) {
571577
handler
@@ -578,21 +584,25 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
578584
recursionTable += (this -> 1)
579585
true
580586
}
581-
} else { handler; false }
587+
} else {
588+
handler
589+
false
590+
}
582591
} else {
583592
_rawflags |= LOCKED
584593
true
585594
}
586595
}
587596

588597
// Unlock a symbol
589-
private[scala] def unlock() = {
598+
private[scala] def unlock(): Unit =
590599
if ((_rawflags & LOCKED) != 0L) {
591600
_rawflags &= ~LOCKED
601+
if (lockTracing && !_lockingTrace.isEmpty)
602+
_lockingTrace.remove(index = _lockingTrace.size - 1, count = 1) // dropRightInPlace(1)
592603
if (settings.Yrecursion.value != 0)
593604
recursionTable -= this
594605
}
595-
}
596606

597607
// ----- tests ----------------------------------------------------------------------
598608

@@ -1553,9 +1563,16 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
15531563
if ((_rawflags & LOCKED) != 0L) { // rolled out once for performance
15541564
lock {
15551565
setInfo(ErrorType)
1556-
throw CyclicReference(this, tp)
1566+
val trace =
1567+
if (lockTracing) {
1568+
val t = _lockingTrace.toArray
1569+
_lockingTrace.clear()
1570+
t
1571+
} else CyclicReference.emptyTrace
1572+
throw CyclicReference(this, tp, trace)
15571573
}
15581574
} else {
1575+
if (lockTracing) _lockingTrace.addOne(this)
15591576
_rawflags |= LOCKED
15601577
}
15611578
val current = phase
@@ -1568,18 +1585,15 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
15681585
unlock()
15691586
phase = current
15701587
}
1571-
} else {
1572-
// In runtime reflection, there is only on phase, so don't mutate Global.phase which would lead to warnings
1573-
// of data races from when using TSAN to assess thread safety.
1574-
try {
1575-
tp.complete(this)
1576-
} finally {
1577-
unlock()
1578-
}
15791588
}
1589+
else
1590+
// In runtime reflection, there is only one phase, so don't mutate Global.phase
1591+
// which would lead to warnings of data races from when using TSAN to assess thread safety.
1592+
try tp.complete(this)
1593+
finally unlock()
15801594
} catch {
15811595
case ex: CyclicReference =>
1582-
devWarning("... hit cycle trying to complete " + this.fullLocationString)
1596+
devWarning(s"... hit cycle trying to complete $fullLocationString")
15831597
throw ex
15841598
}
15851599

@@ -3839,10 +3853,13 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
38393853
else closestEnclMethod(from.owner)
38403854

38413855
/** An exception for cyclic references of symbol definitions */
3842-
case class CyclicReference(sym: Symbol, info: Type)
3843-
extends TypeError("illegal cyclic reference involving " + sym) {
3856+
case class CyclicReference(sym: Symbol, info: Type, trace: Array[Symbol] = CyclicReference.emptyTrace)
3857+
extends TypeError(s"illegal cyclic reference involving $sym") {
38443858
if (settings.isDebug) printStackTrace()
38453859
}
3860+
object CyclicReference {
3861+
val emptyTrace: Array[Symbol] = Array.empty[Symbol]
3862+
}
38463863

38473864
/** A class for type histories */
38483865
private final case class TypeHistory protected (private var _validFrom: Period, private var _info: Type, private var _prev: TypeHistory) {

test/files/neg/t7808.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
t7808.scala:5: error: recursive value ls needs type
2+
val (ls, rs) = z match {
3+
^
4+
1 error

test/files/neg/t7808.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//> using options -Vcyclic
2+
class C {
3+
type OI = Option[Int]
4+
def f(z: OI, ls: List[OI], rs: List[OI]): (List[OI], List[OI]) = {
5+
val (ls, rs) = z match {
6+
case Some(_) => (z::ls, rs)
7+
case _ => (ls, z::rs)
8+
}
9+
(ls, rs)
10+
}
11+
}
12+
13+
/*
14+
t7808.scala:5: error: recursive value x$1 needs type
15+
val (ls, rs) = z match {
16+
^
17+
1 error
18+
*/

test/files/neg/t7808b.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
t7808b.scala:5: error: recursive value x$1 needs type; value x$1 is synthetic; use -Vcyclic to find which definition needs an explicit type
2+
val (ls, rs) = z match {
3+
^
4+
1 error

test/files/neg/t7808b.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
class C {
3+
type OI = Option[Int]
4+
def f(z: OI, ls: List[OI], rs: List[OI]): (List[OI], List[OI]) = {
5+
val (ls, rs) = z match {
6+
case Some(_) => (z::ls, rs)
7+
case _ => (ls, z::rs)
8+
}
9+
(ls, rs)
10+
}
11+
}
12+
13+
/*
14+
t7808.scala:5: error: recursive value x$1 needs type
15+
val (ls, rs) = z match {
16+
^
17+
1 error
18+
*/

0 commit comments

Comments
 (0)