Skip to content

Commit f901907

Browse files
Backport "Add line number magic comment support" to 3.7.3 (#23646)
Backports #23549 to the 3.7.3-RC1. PR submitted by the release tooling.
2 parents 952b866 + 10f0941 commit f901907

16 files changed

+123
-4
lines changed

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

Lines changed: 11 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,15 @@ 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+
if span.start >= offset then // This span is in user code
60+
originalFile.atSpan(span.shift(-offset))
61+
else // Otherwise, return the source position in the wrapper code
62+
source.atSpan(span)
63+
case _ => source.atSpan(span)
5564

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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ private sealed trait YSettings:
444444
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.")
445445
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.")
446446

447+
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.", "")
448+
447449
// Experimental language features
448450
@deprecated(message = "This flag has no effect and will be removed in a future version.", since = "3.7.0")
449451
val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism. (This flag has no effect)", deprecation = Deprecation.removed())

compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import core.Decorators.*
1111
import printing.Highlighting.{Blue, Red, Yellow}
1212
import printing.SyntaxHighlighting
1313
import Diagnostic.*
14-
import util.{ SourcePosition, NoSourcePosition }
14+
import util.{SourcePosition, NoSourcePosition}
1515
import util.Chars.{ LF, CR, FF, SU }
1616
import scala.annotation.switch
1717

compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import core.Contexts.*
77
import collection.mutable
88
import scala.annotation.tailrec
99
import dotty.tools.dotc.reporting.Reporter
10-
import dotty.tools.dotc.util.SourcePosition;
10+
import dotty.tools.dotc.util.SourcePosition
1111

1212
import java.io.OutputStreamWriter
1313
import java.nio.charset.StandardCharsets.UTF_8

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

Lines changed: 31 additions & 0 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.*
@@ -61,6 +62,36 @@ object ScriptSourceFile {
6162
}
6263
}
6364

65+
object WrappedSourceFile:
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
77+
else
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
92+
val result = cache.getOrElseUpdate(sourceFile, findOffset)
93+
result
94+
6495
class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends interfaces.SourceFile {
6596
import SourceFile.*
6697

tests/neg/magic-offset-header-a.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
def test1(): Int = "无穷" // error
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-a.scala:2:19 ----------------------------------------------
2+
2 |def test1(): Int = "无穷" // error
3+
| ^^^^
4+
| Found: ("无穷" : String)
5+
| Required: Int
6+
|
7+
| longer explanation available when compiling with `-explain`
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//> using options -Ymagic-offset-header:TEST_MARKER
2+
val t1 = 1
3+
val t2 = 2
4+
val t3 = 3
5+
///TEST_MARKER:tests/neg/magic-offset-header-a.scala
6+
7+
def test1(): Int = "无穷" // anypos-error

tests/neg/magic-offset-header-b.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
def y: Int = false // error
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b_wrapper.scala:3:13 --------------------------------------
2+
3 |def x: Int = true // error
3+
| ^^^^
4+
| Found: (true : Boolean)
5+
| Required: Int
6+
|
7+
| longer explanation available when compiling with `-explain`
8+
-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:2:13 ----------------------------------------------
9+
2 |def y: Int = false // error
10+
| ^^^^^
11+
| Found: (false : Boolean)
12+
| Required: Int
13+
|
14+
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)