Skip to content

Commit 48c4322

Browse files
committed
Reposition span to original file for wrapper scripts
1 parent 36aac1e commit 48c4322

File tree

3 files changed

+36
-14
lines changed

3 files changed

+36
-14
lines changed

compiler/src/dotty/tools/dotc/ast/Positioned.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package dotc
33
package ast
44

55
import util.Spans.*
6-
import util.{SourceFile, SourcePosition, SrcPos}
6+
import util.{SourceFile, SourcePosition, SrcPos, WrappedSourceFile}
7+
import WrappedSourceFile.MagicHeaderInfo, MagicHeaderInfo.*
78
import core.Contexts.*
89
import core.Decorators.*
910
import core.NameOps.*
@@ -51,7 +52,12 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src
5152

5253
def source: SourceFile = mySource
5354

54-
def sourcePos(using Context): SourcePosition = source.atSpan(span)
55+
def sourcePos(using Context): SourcePosition =
56+
val info = WrappedSourceFile.locateMagicHeader(source)
57+
info match
58+
case HasHeader(offset, originalFile) =>
59+
originalFile.atSpan(span `shift` -offset)
60+
case _ => source.atSpan(span)
5561

5662
/** This positioned item, widened to `SrcPos`. Used to make clear we only need the
5763
* position, typically for error reporting.

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ private sealed trait YSettings:
443443
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.")
444444
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.")
445445

446-
val YmagicOffsetHeader: Setting[String] = StringSetting(AdvancedSetting, "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 can be optionally suffixed by `:<ORIGINAL_FILE_PATH>` to indicate the original file.", "")
446+
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:<ORIGINAL_FILE_PATH>` marks the start of user code. The comment should be suffixed by `:<ORIGINAL_FILE_PATH>` to indicate the original file.", "")
447447

448448
// Experimental language features
449449
@deprecated(message = "This flag has no effect and will be removed in a future version.", since = "3.7.0")

compiler/src/dotty/tools/dotc/util/SourceFile.scala

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import scala.language.unsafeNulls
77
import dotty.tools.io.*
88
import Spans.*
99
import core.Contexts.*
10+
import core.Decorators.*
1011

1112
import scala.io.Codec
1213
import Chars.*
@@ -62,19 +63,34 @@ object ScriptSourceFile {
6263
}
6364

6465
object WrappedSourceFile:
65-
private val cache: mutable.HashMap[SourceFile, Int] = mutable.HashMap.empty
66-
def locateMagicHeader(sourceFile: SourceFile)(using Context): Option[Int] =
67-
def findOffset: Int =
68-
val magicHeader = ctx.settings.XmagicOffsetHeader.value
69-
if magicHeader.isEmpty then
70-
-1
66+
enum MagicHeaderInfo:
67+
case HasHeader(offset: Int, originalFile: SourceFile)
68+
case NoHeader
69+
import MagicHeaderInfo.*
70+
71+
private val cache: mutable.HashMap[SourceFile, MagicHeaderInfo] = mutable.HashMap.empty
72+
73+
def locateMagicHeader(sourceFile: SourceFile)(using Context): MagicHeaderInfo =
74+
def findOffset: MagicHeaderInfo =
75+
val magicHeader = ctx.settings.YmagicOffsetHeader.value
76+
if magicHeader.isEmpty then NoHeader
7177
else
72-
val s = new String(sourceFile.content)
73-
val regex = ("(?m)^" + java.util.regex.Pattern.quote(magicHeader) + "$").r
74-
val pos = regex.findFirstMatchIn(s).map(_.start).map(sourceFile.offsetToLine(_))
75-
pos.getOrElse(-1)
78+
val text = new String(sourceFile.content)
79+
val headerQuoted = java.util.regex.Pattern.quote("///" + magicHeader)
80+
val regex = s"(?m)^$headerQuoted:(.+)$$".r
81+
regex.findFirstMatchIn(text) match
82+
case Some(m) =>
83+
val markerOffset = m.start
84+
val sourceStartOffset = sourceFile.nextLine(markerOffset)
85+
val file = ctx.getFile(m.group(1))
86+
if file.exists then
87+
HasHeader(sourceStartOffset, ctx.getSource(file))
88+
else
89+
report.warning(em"original source file not found: ${file.path}")
90+
NoHeader
91+
case None => NoHeader
7692
val result = cache.getOrElseUpdate(sourceFile, findOffset)
77-
if result >= 0 then Some(result + 1) else None
93+
result
7894

7995
class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends interfaces.SourceFile {
8096
import SourceFile.*

0 commit comments

Comments
 (0)