diff --git a/compiler/src/dotty/tools/dotc/transform/init/CycleChecker.scala b/compiler/src/dotty/tools/dotc/transform/init/CycleChecker.scala index 35dcf09a3342..38baa4e6ddef 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/CycleChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/CycleChecker.scala @@ -147,6 +147,7 @@ class CycleChecker(cache: Cache) { val ctor = obj.moduleClass.primaryConstructor var trace = state.trace.dropWhile(_.symbol != ctor) :+ dep + val pinpointOpt = trace.find(_.isInstanceOf[InstanceUsage]) val traceSuppress = trace.size > traceNumberLimit if traceSuppress then // truncate trace up to the first escape of object @@ -159,10 +160,16 @@ class CycleChecker(cache: Cache) { trace = trace :+ elem val locations = trace.flatMap(_.source) - if cycle.size > 1 then - CyclicObjectInit(cycle, locations, traceSuppress) :: Nil - else - ObjectLeakDuringInit(obj, locations, traceSuppress) :: Nil + val warning = + if cycle.size > 1 then + CyclicObjectInit(cycle, locations, traceSuppress) + else + ObjectLeakDuringInit(obj, locations, traceSuppress) + + if pinpointOpt.nonEmpty then + warning.pinpoint(pinpointOpt.get.source.last, "Leaking the object may cause initialization problems") + + warning :: Nil else val constr = obj.moduleClass.primaryConstructor state.visitObject(dep) { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index 3ea71307098a..f11440287027 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -8,6 +8,9 @@ import ast.tpd._ import core._ import Decorators._, printing.SyntaxHighlighting import Types._, Symbols._, Contexts._ +import util.{ SimpleIdentityMap, SourcePosition } + +import reporting.MessageRendering import Effects._, Potentials._ @@ -30,10 +33,31 @@ object Errors { def toErrors: Errors = this :: Nil + /** pinpoints in stacktrace */ + private var pinpoints: SimpleIdentityMap[Tree, String] = SimpleIdentityMap.empty + + def pinpoint(tree: Tree, msg: String): this.type = + this.pinpoints = this.pinpoints.updated(tree, msg) + this + private def stacktracePrefix: String = val str = if traceSuppressed then "suppressed" else "full" " Calling trace (" + str + "):\n" + private val render = new MessageRendering {} + + private def pinpointText(pos: SourcePosition, msg: String, offset: Int)(using Context): String = + val carets = render.hl("Warning") { + if (pos.startLine == pos.endLine) + "^" * math.max(1, pos.endColumn - pos.startColumn) + else "^" + } + + val padding = pos.startColumnPadding + (" " * offset) + val marker = padding + carets + val textline = padding + msg + "\n" + marker + "\n" + textline + def stacktrace(using Context): String = if (trace.isEmpty) "" else stacktracePrefix + { var indentCount = 0 var last: String = "" @@ -45,8 +69,12 @@ object Errors { val line = if pos.source.exists then val loc = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]" - val code = SyntaxHighlighting.highlight(pos.lineContent.trim) - i"$code\t$loc" + val code = SyntaxHighlighting.highlight(pos.lineContent.stripLineEnd) + val pinpoint = + if !pinpoints.contains(tree) then "" + else pinpointText(pos, pinpoints(tree), indentCount + 3) + + i"$code\t$loc" + pinpoint else tree.show