From 39fa7dd1ae1690346023a04788a460fe6b88b1e4 Mon Sep 17 00:00:00 2001 From: Mike Bland Date: Wed, 17 Dec 2025 16:19:12 -0500 Subject: [PATCH] Fix -Wconf:src Windows path conversion Updates the -Wconf:src filter to avoid using `java.nio.file.Path.toURI` in order to fix Windows source path conversions. `Path.toURI` prepends the current working directory to Windows-like paths unconditionally, and converts backslashes in such paths to `%5C` escape sequences This can cause `-Wconf:src` filters that work on non-Windows platforms to fail on Windows. For example, before this change, the `Path.toURI` conversion in the `SourcePattern` case from `MessageFilter.matches()` produced: ```txt original: Optional[C:\foo\bar\myfile.scala] resolved: /Users/mbland/src/scala/scala3/C:%5Cfoo%5Cbar%5Cmyfile.scala ``` After this change, it produces the following, which still prepends the current working directory, but properly converts path separators to `/`: ```txt original: Optional[C:\foo\bar\myfile.scala] resolved: /Users/mbland/src/scala/scala3/C:/foo/bar/myfile.scala ``` This change is based on scala/scala#11192, and also adapts some test cases from that pull request to validate symlink and normalized path handling. This change also extracts the `diagnosticWarning` helper method to reduce duplication between new and existing test cases. --- .../dotty/tools/dotc/reporting/WConf.scala | 3 +- .../dotc/config/ScalaSettingsTests.scala | 90 ++++++++++++------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 42258f67938a..770f17564bf1 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -30,8 +30,9 @@ enum MessageFilter: case SourcePattern(pattern) => val source = message.position.orElse(NoSourcePosition).source() val path = source.jfile() - .map(_.toPath.toAbsolutePath.toUri.normalize().getRawPath) + .map(_.toPath.toAbsolutePath.normalize.toString) .orElse(source.path()) + .replace("\\", "/") pattern.findFirstIn(path).nonEmpty case Origin(pattern) => message match diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index c36e15a0eb36..e04a7c613bdf 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -10,8 +10,8 @@ import core.Decorators.toMessage import dotty.tools.io.{Path, PlainFile} import java.net.URI -import java.nio.file.Files -import scala.util.Using +import java.nio.file.{Files, Paths} +import scala.util.{Success, Using} import scala.annotation.nowarn @@ -215,15 +215,16 @@ class ScalaSettingsTests: val wconf = reporting.WConf.fromSettings(wconfStr) wconf.map(_.action(warning)) + private def diagnosticWarning(source: util.SourceFile) = reporting.Diagnostic.Warning( + "A warning".toMessage, + util.SourcePosition(source = source, span = util.Spans.Span(1L)) + ) + @Test def `WConf src filter silences warnings from a matching path for virtual file`: Unit = val result = wconfSrcFilterTest( argsStr = "-Wconf:src=path/.*:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""), - span = util.Spans.Span(1L) - ) + warning = diagnosticWarning( + util.SourceFile.virtual(new URI("file:///some/path/file.scala"), "") ) ) assertEquals(result, Right(reporting.Action.Silent)) @@ -231,12 +232,8 @@ class ScalaSettingsTests: @Test def `WConf src filter doesn't silence warnings from a non-matching path`: Unit = val result = wconfSrcFilterTest( argsStr = "-Wconf:src=another/.*:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""), - span = util.Spans.Span(1L) - ) + warning = diagnosticWarning( + util.SourceFile.virtual(new URI("file:///some/path/file.scala"), "") ) ) assertEquals(result, Right(reporting.Action.Warning)) @@ -245,12 +242,8 @@ class ScalaSettingsTests: val result = Using.resource(Files.createTempFile("myfile", ".scala").nn) { file => wconfSrcFilterTest( argsStr = "-Wconf:src=myfile.*?\\.scala:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile(new PlainFile(Path(file)), "UTF-8"), - span = util.Spans.Span(1L) - ) + warning = diagnosticWarning( + util.SourceFile(new PlainFile(Path(file)), "UTF-8") ) ) }(using Files.deleteIfExists(_)) @@ -260,27 +253,58 @@ class ScalaSettingsTests: val result = Using.resource(Files.createTempFile("myfile", ".scala").nn) { file => wconfSrcFilterTest( argsStr = "-Wconf:src=another.*?\\.scala:s", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile(new PlainFile(Path(file)), "UTF-8"), - span = util.Spans.Span(1L) - ) + warning = diagnosticWarning( + util.SourceFile(new PlainFile(Path(file)), "UTF-8") ) ) }(using Files.deleteIfExists(_)) assertEquals(result, Right(reporting.Action.Warning)) + @Test def `Wconf src filter matches symbolic links without resolving them`: Unit = + val result = Using.Manager { use => + def f(file: java.nio.file.Path) = use(file)(using Files.deleteIfExists(_)) + + val tempDir = f(Files.createTempDirectory("wconf-src-symlink-test")) + val externalDir = f(Files.createDirectory(Paths.get(tempDir.toString, "external"))) + val cacheDir = f(Files.createDirectory(Paths.get(tempDir.toString, "cache"))) + val actualFile = f(Files.createFile(Paths.get(cacheDir.toString, "myfile.scala"))) + val symlinkPath = Paths.get(externalDir.toString, "myfile.scala") + + // This may fail with an IOException if symlinks are disabled on Windows: + // https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createSymbolicLink-java.nio.file.Path-java.nio.file.Path-java.nio.file.attribute.FileAttribute...- + val symlink = f(Files.createSymbolicLink(symlinkPath, actualFile)) + + wconfSrcFilterTest( + argsStr = "-Wconf:src=external/.*\\.scala:s", + warning = diagnosticWarning( + util.SourceFile(new PlainFile(Path(symlink)), "UTF-8"), + ) + ) + } + assertEquals(result, Success(Right(reporting.Action.Silent))) + + @Test def `Wconf src filter handles Windows paths`: Unit = + val path = Path("C:\\foo\\bar\\myfile.scala") + val result = wconfSrcFilterTest( + argsStr = "-Wconf:src=foo/bar/.*\\.scala:s", + warning = diagnosticWarning(util.SourceFile(new PlainFile(path), "UTF-8")) + ) + assertEquals(result, Right(reporting.Action.Silent)) + + @Test def `Wconf src filter normalizes paths`: Unit = + val path = Path("foo/./bar/../quux/../baz/File.scala") + val result = wconfSrcFilterTest( + argsStr = "-Wconf:src=foo/baz/.*\\.scala:s", + warning = diagnosticWarning(util.SourceFile(new PlainFile(path), "UTF-8")) + ) + assertEquals(result, Right(reporting.Action.Silent)) + @Test def `WConf src filter reports an error on an invalid regex`: Unit = val result = wconfSrcFilterTest( argsStr = """-Wconf:src=\:s""", - warning = reporting.Diagnostic.Warning( - "A warning".toMessage, - util.SourcePosition( - source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""), - span = util.Spans.Span(1L) - ) - ), + warning = diagnosticWarning( + util.SourceFile.virtual(new URI("file:///some/path/file.scala"), "") + ) ) assertTrue( result.left.exists(errors =>