Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backend computes line number from source of position #21763

Merged
merged 3 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,13 @@ trait BCodeSkelBuilder extends BCodeHelpers {
}

if (emitLines && tree.span.exists && !tree.hasAttachment(SyntheticUnit)) {
val nr = ctx.source.offsetToLine(tree.span.point) + 1
val nr =
val sourcePos = tree.sourcePos
(
if sourcePos.exists then sourcePos.source.positionInUltimateSource(sourcePos).line
else ctx.source.offsetToLine(tree.span.point) // fallback
) + 1

if (nr != lastEmittedLineNr) {
lastEmittedLineNr = nr
getNonLabelNode(lastInsn) match {
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/util/SourceFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends
* For regular source files, simply return the argument.
*/
def positionInUltimateSource(position: SourcePosition): SourcePosition =
SourcePosition(underlying, position.span shift start)
if isSelfContained then position // return the argument
else SourcePosition(underlying, position.span shift start)

private def calculateLineIndicesFromContents() = {
val cs = content()
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/util/SourcePosition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ extends SrcPos, interfaces.SourcePosition, Showable {
rec(this)
}


override def toString: String =
s"${if (source.exists) source.file.toString else "(no source)"}:$span"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,18 @@ class BootstrappedOnlyCompilationTests {

// 1. hack with absolute path for -Xplugin
// 2. copy `pluginFile` to destination
def compileFilesInDir(dir: String): CompilationTest = {
def compileFilesInDir(dir: String, run: Boolean = false): CompilationTest = {
val outDir = defaultOutputDir + "testPlugins/"
val sourceDir = new java.io.File(dir)

val dirs = sourceDir.listFiles.toList.filter(_.isDirectory)
val targets = dirs.map { dir =>
val compileDir = createOutputDirsForDir(dir, sourceDir, outDir)
Files.copy(dir.toPath.resolve(pluginFile), compileDir.toPath.resolve(pluginFile), StandardCopyOption.REPLACE_EXISTING)
val flags = TestFlags(withCompilerClasspath, noCheckOptions).and("-Xplugin:" + compileDir.getAbsolutePath)
val flags = {
val base = TestFlags(withCompilerClasspath, noCheckOptions).and("-Xplugin:" + compileDir.getAbsolutePath)
if run then base.withRunClasspath(withCompilerClasspath) else base
}
SeparateCompilationSource("testPlugins", dir, flags, compileDir)
}

Expand All @@ -210,6 +213,7 @@ class BootstrappedOnlyCompilationTests {

compileFilesInDir("tests/plugins/neg").checkExpectedErrors()
compileDir("tests/plugins/custom/analyzer", withCompilerOptions.and("-Yretain-trees")).checkCompile()
compileFilesInDir("tests/plugins/run", run = true).checkRuns()
}
}

Expand Down
3 changes: 3 additions & 0 deletions tests/plugins/run/scriptWrapper/Framework_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package framework

class entrypoint extends scala.annotation.Annotation
68 changes: 68 additions & 0 deletions tests/plugins/run/scriptWrapper/LineNumberPlugin_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package scriptWrapper

import dotty.tools.dotc.*
import core.*
import Contexts.Context
import Contexts.ctx
import plugins.*
import ast.tpd
import util.SourceFile

class LineNumberPlugin extends StandardPlugin {
val name: String = "linenumbers"
val description: String = "adjusts line numbers of script files"

override def initialize(options: List[String])(using Context): List[PluginPhase] = FixLineNumbers() :: Nil
}

// Loosely follows Mill linenumbers plugin (scan for marker with "original" source, adjust line numbers to match)
class FixLineNumbers extends PluginPhase {

val codeMarker = "//USER_CODE_HERE"

def phaseName: String = "fixLineNumbers"
override def runsAfter: Set[String] = Set("posttyper")
override def runsBefore: Set[String] = Set("pickler")

override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = {
val sourceContent = ctx.source.content()
val lines = new String(sourceContent).linesWithSeparators.toVector
val codeMarkerLine = lines.indexWhere(_.startsWith(codeMarker))

if codeMarkerLine < 0 then
tree
else
val adjustedFile = lines.collectFirst {
case s"//USER_SRC_FILE:./$file" => file.trim
}.getOrElse("<unknown>")

val adjustedSrc = ctx.source.file.container.lookupName(adjustedFile, directory = false) match
case null =>
report.error(s"could not find file $adjustedFile", tree.sourcePos)
return tree
case file =>
SourceFile(file, scala.io.Codec.UTF8)

val userCodeOffset = ctx.source.lineToOffset(codeMarkerLine + 1) // lines.take(codeMarkerLine).map(_.length).sum
val lineMapper = LineMapper(codeMarkerLine, userCodeOffset, adjustedSrc)
lineMapper.transform(tree)
}

}

class LineMapper(markerLine: Int, userCodeOffset: Int, adjustedSrc: SourceFile) extends tpd.TreeMapWithPreciseStatContexts() {

override def transform(tree: tpd.Tree)(using Context): tpd.Tree = {
val tree0 = super.transform(tree)
val pos = tree0.sourcePos
if pos.exists && pos.start >= userCodeOffset then
val tree1 = tree0.cloneIn(adjustedSrc).withSpan(pos.span.shift(-userCodeOffset))
// if tree1.show.toString == "???" then
// val pos1 = tree1.sourcePos
// sys.error(s"rewrote ??? at ${pos1.source}:${pos1.line + 1}:${pos1.column + 1} (sourced from ${markerLine + 2})")
tree1
else
tree0
}

}
25 changes: 25 additions & 0 deletions tests/plugins/run/scriptWrapper/Test_3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@main def Test: Unit = {
val mainCls = Class.forName("foo_sc")
val mainMethod = mainCls.getMethod("main", classOf[Array[String]])
val stackTrace: Array[String] = {
try
mainMethod.invoke(null, Array.empty[String])
sys.error("Expected an exception")
catch
case e: java.lang.reflect.InvocationTargetException =>
val cause = e.getCause
if cause != null then
cause.getStackTrace.map(_.toString)
else
throw e
}

val expected = Set(
"foo_sc$.getRandom(foo_2.scala:3)", // adjusted line number (11 -> 3)
"foo_sc$.brokenRandom(foo_2.scala:5)", // adjusted line number (13 -> 5)
"foo_sc$.run(foo_2.scala:8)", // adjusted line number (16 -> 8)
)

val missing = expected -- stackTrace
assert(missing.isEmpty, s"Missing: $missing")
}
18 changes: 18 additions & 0 deletions tests/plugins/run/scriptWrapper/foo_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// generated code
// script: foo.sc
object foo_sc {
def main(args: Array[String]): Unit = {
run // assume some macro generates this by scanning for @entrypoint
}
//USER_SRC_FILE:./foo_original_2.scala
//USER_CODE_HERE
import framework.*

def getRandom: Int = brokenRandom // LINE 3;

def brokenRandom: Int = ??? // LINE 5;

@entrypoint
def run = println("Hello, here is a random number: " + getRandom) // LINE 8;
//END_USER_CODE_HERE
}
8 changes: 8 additions & 0 deletions tests/plugins/run/scriptWrapper/foo_original_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import framework.*

def getRandom: Int = brokenRandom // LINE 3;

def brokenRandom: Int = ??? // LINE 5;

@entrypoint
def run = println("Hello, here is a random number: " + getRandom) // LINE 8;
1 change: 1 addition & 0 deletions tests/plugins/run/scriptWrapper/plugin.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pluginClass=scriptWrapper.LineNumberPlugin