From 2cb6965aaa8cf9b9fe0ce025a8b75f569bbdffd1 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 15 Sep 2025 11:42:54 +0200 Subject: [PATCH 1/4] Skip caret when source is missing --- .../tools/dotc/transform/init/Trace.scala | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala index 9a00589197d5..ee1e231299bb 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,30 @@ 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 + val loc = pos.source.file.name + ":" + (pos.line + 1) + if hasSource then + val code = SyntaxHighlighting.highlight(pos.lineContent.trim) + i"$code\t[ $loc ]" + else + loc + else tree match case defDef: DefTree => @@ -62,7 +76,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 "" From 7393a9b3b9146229ced6f0db5b1e74b73f16728e Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 15 Sep 2025 13:53:07 +0200 Subject: [PATCH 2/4] Add test --- .../test/dotty/tools/dotc/CompilationTests.scala | 16 +++++++++++++++- tests/init-global/special/tastySource/A.scala | 4 ++++ tests/init-global/special/tastySource/B.check | 13 +++++++++++++ tests/init-global/special/tastySource/B.scala | 7 +++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/init-global/special/tastySource/A.scala create mode 100644 tests/init-global/special/tastySource/B.check create mode 100644 tests/init-global/special/tastySource/B.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 214a1e953ef0..30fc447e78f1 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/tastySource") + 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..09739efe7870 --- /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 ] + |│ ^ + |├── A.scala:2 + |├── 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 From 9959ff71206563a1b5ea5e80acc0a974f49b9115 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 15 Sep 2025 20:19:09 +0200 Subject: [PATCH 3/4] Update trace format for consistency --- .../src/dotty/tools/dotc/transform/init/Trace.scala | 11 ++++++----- tests/init-global/special/tastySource/B.check | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala index ee1e231299bb..dd79e8fbdb2e 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala @@ -61,12 +61,13 @@ object Trace: val line = if pos.exists then val loc = pos.source.file.name + ":" + (pos.line + 1) - if hasSource then - val code = SyntaxHighlighting.highlight(pos.lineContent.trim) - i"$code\t[ $loc ]" - else - loc + val code = + if hasSource then + SyntaxHighlighting.highlight(pos.lineContent.trim) + else + "(no source)" + i"$code\t[ $loc ]" else tree match case defDef: DefTree => diff --git a/tests/init-global/special/tastySource/B.check b/tests/init-global/special/tastySource/B.check index 09739efe7870..56d274d49579 100644 --- a/tests/init-global/special/tastySource/B.check +++ b/tests/init-global/special/tastySource/B.check @@ -5,8 +5,8 @@ |Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace: |├── object B: [ B.scala:1 ] |│ ^ - |├── A.scala:2 - |├── A.scala:4 + |├── (no source) [ A.scala:2 ] + |├── (no source) [ A.scala:4 ] |├── var y = A.foo(bar) * 2 [ B.scala:2 ] |│ ^^^ |└── def bar = C.n * 3 // warn [ B.scala:4 ] From bac5c819e75ea907bf8fdc93ce78a2f24ee0f437 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Mon, 15 Sep 2025 21:00:55 +0200 Subject: [PATCH 4/4] Show full path for external code without source --- compiler/src/dotty/tools/dotc/transform/init/Trace.scala | 4 +++- compiler/test/dotty/tools/dotc/CompilationTests.scala | 2 +- tests/init-global/special/tastySource/B.check | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala index dd79e8fbdb2e..910c8be53367 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala @@ -60,7 +60,9 @@ object Trace: val hasSource = fileExists(pos.source) val line = if pos.exists then - val loc = pos.source.file.name + ":" + (pos.line + 1) + // 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) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 30fc447e78f1..2663b4d5b10b 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -259,7 +259,7 @@ class CompilationTests { 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/tastySource") + 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() diff --git a/tests/init-global/special/tastySource/B.check b/tests/init-global/special/tastySource/B.check index 56d274d49579..ece8bc60b7d4 100644 --- a/tests/init-global/special/tastySource/B.check +++ b/tests/init-global/special/tastySource/B.check @@ -5,8 +5,8 @@ |Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace: |├── object B: [ B.scala:1 ] |│ ^ - |├── (no source) [ A.scala:2 ] - |├── (no source) [ A.scala:4 ] + |├── (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 ]