diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 248eaf73931e..47e3e8aa4b07 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -341,6 +341,7 @@ private sealed trait XSettings: val Xdumpclasses: Setting[String] = StringSetting(AdvancedSetting, "Xdump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") val XjarCompressionLevel: Setting[Int] = IntChoiceSetting(AdvancedSetting, "Xjar-compression-level", "compression level to use when writing jar files", Deflater.DEFAULT_COMPRESSION to Deflater.BEST_COMPRESSION, Deflater.DEFAULT_COMPRESSION) val XkindProjector: Setting[String] = ChoiceSetting(AdvancedSetting, "Xkind-projector", "[underscores, enable, disable]", "Allow `*` as type lambda placeholder to be compatible with kind projector. When invoked as -Xkind-projector:underscores will repurpose `_` to be a type parameter placeholder, this will disable usage of underscore as a wildcard.", List("disable", "", "underscores"), "disable", legacyArgs = true) + val XmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "Xmagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Xmagic-offset-header:///SOURCE_CODE_START", "") /** Documentation related settings */ val XdropComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdrop-docs", "Drop documentation when scanning source files.", aliases = List("-Xdrop-comments")) diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 7fddfc8d6ed0..dcb9ddcb2d06 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, WrappedSourceFile } import util.Chars.{ LF, CR, FF, SU } import scala.annotation.switch @@ -44,7 +44,10 @@ trait MessageRendering { var maxLen = Int.MinValue def render(offsetAndLine: (Int, String)): String = { val (offset1, line) = offsetAndLine - val lineNbr = (pos.source.offsetToLine(offset1) + 1).toString + var magicOffset = WrappedSourceFile.locateMagicHeader(pos.source).getOrElse(0) + val lineId = pos.source.offsetToLine(offset1) + if lineId < magicOffset then magicOffset = 0 + val lineNbr = (lineId + 1 - magicOffset).toString val prefix = String.format(s"%${offset - 2}s |", lineNbr) maxLen = math.max(maxLen, prefix.length) val lnum = hl(" " * math.max(0, maxLen - prefix.length - 1) + prefix) 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..971b614c085a 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -61,6 +61,21 @@ object ScriptSourceFile { } } +object WrappedSourceFile: + private val cache: mutable.HashMap[SourceFile, Int] = mutable.HashMap.empty + def locateMagicHeader(sourceFile: SourceFile)(using Context): Option[Int] = + def findOffset: Int = + val magicHeader = ctx.settings.XmagicOffsetHeader.value + if magicHeader.isEmpty then + -1 + else + val s = new String(sourceFile.content) + val regex = ("(?m)^" + java.util.regex.Pattern.quote(magicHeader) + "$").r + val pos = regex.findFirstMatchIn(s).map(_.start).map(sourceFile.offsetToLine(_)) + pos.getOrElse(-1) + val result = cache.getOrElseUpdate(sourceFile, findOffset) + if result >= 0 then Some(result + 1) else None + class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends interfaces.SourceFile { import SourceFile.* diff --git a/tests/neg/magic-offset-header-a.check b/tests/neg/magic-offset-header-a.check new file mode 100644 index 000000000000..c4db6f3c5a12 --- /dev/null +++ b/tests/neg/magic-offset-header-a.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-a.scala:6:19 ---------------------------------------------- +1 |def test1(): Int = "无穷" // error + | ^^^^ + | Found: ("无穷" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-a.scala b/tests/neg/magic-offset-header-a.scala new file mode 100644 index 000000000000..f125cbb56b70 --- /dev/null +++ b/tests/neg/magic-offset-header-a.scala @@ -0,0 +1,6 @@ +//> using options -Xmagic-offset-header:///TEST_MARKER +val t1 = 1 +val t2 = 2 +val t3 = 3 +///TEST_MARKER +def test1(): Int = "无穷" // error diff --git a/tests/neg/magic-offset-header-b.check b/tests/neg/magic-offset-header-b.check new file mode 100644 index 000000000000..aca2ee070145 --- /dev/null +++ b/tests/neg/magic-offset-header-b.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.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:7: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.scala b/tests/neg/magic-offset-header-b.scala new file mode 100644 index 000000000000..a498335ecd31 --- /dev/null +++ b/tests/neg/magic-offset-header-b.scala @@ -0,0 +1,7 @@ +//> using options -Xmagic-offset-header:///TEST_MARKER + +def x: Int = true // error + +///TEST_MARKER + +def y: Int = false // error diff --git a/tests/neg/magic-offset-header-c.check b/tests/neg/magic-offset-header-c.check new file mode 100644 index 000000000000..504b65b7f95b --- /dev/null +++ b/tests/neg/magic-offset-header-c.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c.scala:8: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.scala b/tests/neg/magic-offset-header-c.scala new file mode 100644 index 000000000000..0e1a1bfca9e3 --- /dev/null +++ b/tests/neg/magic-offset-header-c.scala @@ -0,0 +1,8 @@ +//> using options -Xmagic-offset-header:///SOURCE_CODE_START_MARKER + +val generatedCode = 123 + +///SOURCE_CODE_START_MARKER + +def userCode = + val x: String = 0 // error