diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index d8017783f47f..5b57733eaeb1 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -3,7 +3,8 @@ package dotc package ast import util.Spans.* -import util.{SourceFile, SourcePosition, SrcPos} +import util.{SourceFile, SourcePosition, SrcPos, WrappedSourceFile} +import WrappedSourceFile.MagicHeaderInfo, MagicHeaderInfo.* import core.Contexts.* import core.Decorators.* import core.NameOps.* @@ -51,7 +52,15 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src def source: SourceFile = mySource - def sourcePos(using Context): SourcePosition = source.atSpan(span) + def sourcePos(using Context): SourcePosition = + val info = WrappedSourceFile.locateMagicHeader(source) + info match + case HasHeader(offset, originalFile) => + if span.start >= offset then // This span is in user code + originalFile.atSpan(span.shift(-offset)) + else // Otherwise, return the source position in the wrapper code + source.atSpan(span) + case _ => source.atSpan(span) /** This positioned item, widened to `SrcPos`. Used to make clear we only need the * position, typically for error reporting. diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 248eaf73931e..52315f0cd929 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -443,6 +443,8 @@ private sealed trait YSettings: val YbestEffort: Setting[Boolean] = BooleanSetting(ForkSetting, "Ybest-effort", "Enable best-effort compilation attempting to produce betasty to the META-INF/best-effort directory, regardless of errors, as part of the pickler phase.") val YwithBestEffortTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Ywith-best-effort-tasty", "Allow to compile using best-effort tasty files. If such file is used, the compiler will stop after the pickler phase.") + val YmagicOffsetHeader: Setting[String] = StringSetting(ForkSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START. Then, in the source, the magic comment `///SOURCE_CODE_START:` marks the start of user code. The comment should be suffixed by `:` to indicate the original file.", "") + // Experimental language features @deprecated(message = "This flag has no effect and will be removed in a future version.", since = "3.7.0") val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism. (This flag has no effect)", deprecation = Deprecation.removed()) diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 7fddfc8d6ed0..0414bdb3c58b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -11,7 +11,7 @@ import core.Decorators.* import printing.Highlighting.{Blue, Red, Yellow} import printing.SyntaxHighlighting import Diagnostic.* -import util.{ SourcePosition, NoSourcePosition } +import util.{SourcePosition, NoSourcePosition} import util.Chars.{ LF, CR, FF, SU } import scala.annotation.switch diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 3c7216625a7c..272db26bdd3c 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -7,7 +7,7 @@ import core.Contexts.* import collection.mutable import scala.annotation.tailrec import dotty.tools.dotc.reporting.Reporter -import dotty.tools.dotc.util.SourcePosition; +import dotty.tools.dotc.util.SourcePosition import java.io.OutputStreamWriter import java.nio.charset.StandardCharsets.UTF_8 diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index eb99fe99d926..9cc3dc5e731d 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -7,6 +7,7 @@ import scala.language.unsafeNulls import dotty.tools.io.* import Spans.* import core.Contexts.* +import core.Decorators.* import scala.io.Codec import Chars.* @@ -61,6 +62,36 @@ object ScriptSourceFile { } } +object WrappedSourceFile: + enum MagicHeaderInfo: + case HasHeader(offset: Int, originalFile: SourceFile) + case NoHeader + import MagicHeaderInfo.* + + private val cache: mutable.HashMap[SourceFile, MagicHeaderInfo] = mutable.HashMap.empty + + def locateMagicHeader(sourceFile: SourceFile)(using Context): MagicHeaderInfo = + def findOffset: MagicHeaderInfo = + val magicHeader = ctx.settings.YmagicOffsetHeader.value + if magicHeader.isEmpty then NoHeader + else + val text = new String(sourceFile.content) + val headerQuoted = java.util.regex.Pattern.quote("///" + magicHeader) + val regex = s"(?m)^$headerQuoted:(.+)$$".r + regex.findFirstMatchIn(text) match + case Some(m) => + val markerOffset = m.start + val sourceStartOffset = sourceFile.nextLine(markerOffset) + val file = ctx.getFile(m.group(1)) + if file.exists then + HasHeader(sourceStartOffset, ctx.getSource(file)) + else + report.warning(em"original source file not found: ${file.path}") + NoHeader + case None => NoHeader + val result = cache.getOrElseUpdate(sourceFile, findOffset) + result + class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends interfaces.SourceFile { import SourceFile.* diff --git a/tests/neg/magic-offset-header-a.scala b/tests/neg/magic-offset-header-a.scala new file mode 100644 index 000000000000..48267a7853f0 --- /dev/null +++ b/tests/neg/magic-offset-header-a.scala @@ -0,0 +1,2 @@ + +def test1(): Int = "无穷" // error diff --git a/tests/neg/magic-offset-header-a_wrapper.check b/tests/neg/magic-offset-header-a_wrapper.check new file mode 100644 index 000000000000..0ab253c12804 --- /dev/null +++ b/tests/neg/magic-offset-header-a_wrapper.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-a.scala:2:19 ---------------------------------------------- +2 |def test1(): Int = "无穷" // error + | ^^^^ + | Found: ("无穷" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-a_wrapper.scala b/tests/neg/magic-offset-header-a_wrapper.scala new file mode 100644 index 000000000000..af4f2b8bf8dd --- /dev/null +++ b/tests/neg/magic-offset-header-a_wrapper.scala @@ -0,0 +1,7 @@ +//> using options -Ymagic-offset-header:TEST_MARKER +val t1 = 1 +val t2 = 2 +val t3 = 3 +///TEST_MARKER:tests/neg/magic-offset-header-a.scala + +def test1(): Int = "无穷" // anypos-error diff --git a/tests/neg/magic-offset-header-b.scala b/tests/neg/magic-offset-header-b.scala new file mode 100644 index 000000000000..aeb569272523 --- /dev/null +++ b/tests/neg/magic-offset-header-b.scala @@ -0,0 +1,2 @@ + +def y: Int = false // error diff --git a/tests/neg/magic-offset-header-b_wrapper.check b/tests/neg/magic-offset-header-b_wrapper.check new file mode 100644 index 000000000000..5d862e5a6b10 --- /dev/null +++ b/tests/neg/magic-offset-header-b_wrapper.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b_wrapper.scala:3:13 -------------------------------------- +3 |def x: Int = true // error + | ^^^^ + | Found: (true : Boolean) + | Required: Int + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:2:13 ---------------------------------------------- +2 |def y: Int = false // error + | ^^^^^ + | Found: (false : Boolean) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-b_wrapper.scala b/tests/neg/magic-offset-header-b_wrapper.scala new file mode 100644 index 000000000000..ef0e552948d3 --- /dev/null +++ b/tests/neg/magic-offset-header-b_wrapper.scala @@ -0,0 +1,7 @@ +//> using options -Ymagic-offset-header:TEST_MARKER + +def x: Int = true // error + +///TEST_MARKER:tests/neg/magic-offset-header-b.scala + +def y: Int = false // anypos-error diff --git a/tests/neg/magic-offset-header-c.scala b/tests/neg/magic-offset-header-c.scala new file mode 100644 index 000000000000..be3cb333abff --- /dev/null +++ b/tests/neg/magic-offset-header-c.scala @@ -0,0 +1,3 @@ + +def userCode = + val x: String = 0 // error diff --git a/tests/neg/magic-offset-header-c_wrapper.check b/tests/neg/magic-offset-header-c_wrapper.check new file mode 100644 index 000000000000..0c33f5ea0338 --- /dev/null +++ b/tests/neg/magic-offset-header-c_wrapper.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c.scala:3:18 ---------------------------------------------- +3 | val x: String = 0 // error + | ^ + | Found: (0 : Int) + | Required: String + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-c_wrapper.scala b/tests/neg/magic-offset-header-c_wrapper.scala new file mode 100644 index 000000000000..51804a647fbe --- /dev/null +++ b/tests/neg/magic-offset-header-c_wrapper.scala @@ -0,0 +1,8 @@ +//> using options -Ymagic-offset-header:SOURCE_CODE_START_MARKER + +val generatedCode = 123 + +///SOURCE_CODE_START_MARKER:tests/neg/magic-offset-header-c.scala + +def userCode = + val x: String = 0 // anypos-error diff --git a/tests/neg/magic-offset-header-d_wrapper.check b/tests/neg/magic-offset-header-d_wrapper.check new file mode 100644 index 000000000000..36421e857a1c --- /dev/null +++ b/tests/neg/magic-offset-header-d_wrapper.check @@ -0,0 +1,15 @@ +original source file not found: something_nonexist.scala +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-d_wrapper.scala:3:20 -------------------------------------- +3 |def test1: String = 0 // error + | ^ + | Found: (0 : Int) + | Required: String + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-d_wrapper.scala:5:17 -------------------------------------- +5 |def test2: Int = "0" // error + | ^^^ + | Found: ("0" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-d_wrapper.scala b/tests/neg/magic-offset-header-d_wrapper.scala new file mode 100644 index 000000000000..85840e84b702 --- /dev/null +++ b/tests/neg/magic-offset-header-d_wrapper.scala @@ -0,0 +1,5 @@ +//> using options -Ymagic-offset-header:SOURCE_CODE_START_MARKER + +def test1: String = 0 // error +///SOURCE_CODE_START_MARKER:something_nonexist.scala +def test2: Int = "0" // error