diff --git a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala index 9a00589197d5..910c8be53367 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala @@ -6,6 +6,7 @@ import core.* import Contexts.* import ast.tpd.* import util.SourcePosition +import util.SourceFile import Decorators.*, printing.SyntaxHighlighting @@ -42,17 +43,33 @@ object Trace: inline def extendTrace[T](node: Tree)(using t: Trace)(op: Trace ?=> T): T = op(using t.add(node)) + /** + * Returns whether the source file exists + * + * The method SourceFile#exists always return true thus cannot be used. + */ + def fileExists(source: SourceFile): Boolean = + source.content().nonEmpty + def buildStacktrace(trace: Trace, preamble: String)(using Context): String = if trace.isEmpty then "" else preamble + { var lastLineNum = -1 var lines: mutable.ArrayBuffer[String] = new mutable.ArrayBuffer trace.foreach { tree => val isLastTraceItem = tree `eq` trace.last val pos = tree.sourcePos + val hasSource = fileExists(pos.source) 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" + if pos.exists then + // Show more information for external code without source + val file = if hasSource then pos.source.file.name else pos.source.file.path + val loc = file + ":" + (pos.line + 1) + val code = + if hasSource then + SyntaxHighlighting.highlight(pos.lineContent.trim) + else + "(no source)" + + i"$code\t[ $loc ]" else tree match case defDef: DefTree => @@ -62,7 +79,7 @@ object Trace: tree.show.split(System.lineSeparator(), 2).head val positionMarkerLine = - if pos.exists && pos.source.exists then + if pos.exists && hasSource then (if isLastTraceItem then EMPTY_PADDING else CONNECTING_INDENT)+ positionMarker(pos) else "" diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 214a1e953ef0..2663b4d5b10b 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -243,7 +243,7 @@ class CompilationTests { compileFilesInDir("tests/explicit-nulls/run", explicitNullsOptions) }.checkRuns() - // initialization tests + // initialization tests for global objects @Test def checkInitGlobal: Unit = { implicit val testGroup: TestGroup = TestGroup("checkInitGlobal") compileFilesInDir("tests/init-global/warn", defaultOptions.and("-Ysafe-init-global"), FileFilter.exclude(TestSources.negInitGlobalScala2LibraryTastyExcludelisted)).checkWarnings() @@ -252,6 +252,20 @@ class CompilationTests { compileFilesInDir("tests/init-global/warn-tasty", defaultOptions.and("-Ysafe-init-global"), FileFilter.exclude(TestSources.negInitGlobalScala2LibraryTastyExcludelisted)).checkWarnings() compileFilesInDir("tests/init-global/pos-tasty", defaultOptions.and("-Ysafe-init-global", "-Werror"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() end if + + locally { + val group = TestGroup("checkInitGlobal/tastySource") + val tastSourceOptions = defaultOptions.and("-Ysafe-init-global") + val outDirLib = defaultOutputDir + group + "/A/tastySource/A" + + // Set -sourceroot such that the source code cannot be found by the compiler + val libOptions = tastSourceOptions.and("-sourceroot", "tests/init-global/special") + val lib = compileFile("tests/init-global/special/tastySource/A.scala", libOptions)(group).keepOutput.checkCompile() + + compileFile("tests/init-global/special/tastySource/B.scala", tastSourceOptions.withClasspath(outDirLib))(group).checkWarnings() + + lib.delete() + } } // initialization tests diff --git a/tests/init-global/special/tastySource/A.scala b/tests/init-global/special/tastySource/A.scala new file mode 100644 index 000000000000..44b41588e782 --- /dev/null +++ b/tests/init-global/special/tastySource/A.scala @@ -0,0 +1,4 @@ +object A: + def foo(fn: => Int) = bar(fn) + + def bar(fn: => Int) = fn diff --git a/tests/init-global/special/tastySource/B.check b/tests/init-global/special/tastySource/B.check new file mode 100644 index 000000000000..ece8bc60b7d4 --- /dev/null +++ b/tests/init-global/special/tastySource/B.check @@ -0,0 +1,13 @@ +-- Warning: tests/init-global/special/tastySource/B.scala:4:14 --------------------------------------------------------- +4 | def bar = C.n * 3 // warn + | ^^^ + |Reading mutable state of object C during initialization of object B. + |Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace: + |├── object B: [ B.scala:1 ] + |│ ^ + |├── (no source) [ tastySource/A.scala:2 ] + |├── (no source) [ tastySource/A.scala:4 ] + |├── var y = A.foo(bar) * 2 [ B.scala:2 ] + |│ ^^^ + |└── def bar = C.n * 3 // warn [ B.scala:4 ] + | ^^^ diff --git a/tests/init-global/special/tastySource/B.scala b/tests/init-global/special/tastySource/B.scala new file mode 100644 index 000000000000..cfd4d6c0c5d2 --- /dev/null +++ b/tests/init-global/special/tastySource/B.scala @@ -0,0 +1,7 @@ +object B: + var y = A.foo(bar) * 2 + + def bar = C.n * 3 // warn + +object C: + var n = 10 \ No newline at end of file