Skip to content

Commit dc24b74

Browse files
authored
Merge pull request #14318 from dotty-staging/sjs-fix-ir-positions
Fix #14240: Fix off-by-1 when emitting Scala.js IR Positions.
2 parents 25cdd57 + 2b76faa commit dc24b74

File tree

3 files changed

+49
-3
lines changed

3 files changed

+49
-3
lines changed

compiler/src/dotty/tools/backend/sjs/JSPositions.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ class JSPositions()(using Context) {
3939
private def sourceAndSpan2irPos(source: SourceFile, span: Span): ir.Position = {
4040
if (!span.exists) ir.Position.NoPosition
4141
else {
42-
// dotty positions are 1-based but IR positions are 0-based
42+
// dotty positions and IR positions are both 0-based
4343
val irSource = span2irPosCache.toIRSource(source)
4444
val point = span.point
45-
val line = source.offsetToLine(point) - 1
46-
val column = source.column(point) - 1
45+
val line = source.offsetToLine(point)
46+
val column = source.column(point)
4747
ir.Position(irSource, line, column)
4848
}
4949
}

sbt-test/scalajs/basic/build.sbt

+45
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
lazy val testIRPositions = taskKey[Unit]("test IR positions (#14240)")
2+
13
enablePlugins(ScalaJSPlugin)
24

35
scalaVersion := sys.props("plugin.scalaVersion")
@@ -6,3 +8,46 @@ scalaVersion := sys.props("plugin.scalaVersion")
68
libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.1.0").cross(CrossVersion.for3Use2_13)
79

810
scalaJSUseMainModuleInitializer := true
11+
12+
// #14240 Make sure that generated IR positions are 0-based
13+
testIRPositions := {
14+
import scala.concurrent.{Future, _}
15+
import scala.concurrent.ExecutionContext.Implicits.global
16+
import scala.concurrent.duration._
17+
import scala.util.{Failure, Success}
18+
19+
import org.scalajs.ir.Names._
20+
import org.scalajs.ir.Position
21+
import org.scalajs.ir.Trees._
22+
import org.scalajs.linker.interface.unstable.IRFileImpl
23+
24+
val ir = (Compile / scalaJSIR).value
25+
val classNameToTest = ClassName("test.Main$")
26+
27+
val classDefFuture = {
28+
// This logic is copied from the implementation of `scalajsp` in sbt-scalajs
29+
Future.traverse(ir.data) { irFile =>
30+
val ir = IRFileImpl.fromIRFile(irFile)
31+
ir.entryPointsInfo.map { i =>
32+
if (i.className == classNameToTest) Success(Some(ir))
33+
else Success(None)
34+
}.recover { case t => Failure(t) }
35+
}.flatMap { irs =>
36+
irs.collectFirst {
37+
case Success(Some(f)) => f.tree
38+
}.getOrElse {
39+
val t = new MessageOnlyException(s"class ${classNameToTest.nameString} not found on classpath")
40+
irs.collect { case Failure(st) => t.addSuppressed(st) }
41+
throw t
42+
}
43+
}
44+
}
45+
val classDef = Await.result(classDefFuture, Duration.Inf)
46+
47+
def testPos(pos: Position, expectedLine: Int, expectedColumn: Int): Unit = {
48+
if (!pos.source.getPath.endsWith("/Main.scala") || pos.line != expectedLine || pos.column != expectedColumn)
49+
throw new MessageOnlyException(s"Expected Main.scala@$expectedLine:$expectedColumn but got $pos")
50+
}
51+
52+
testPos(classDef.pos, 5, 7)
53+
}

sbt-test/scalajs/basic/test

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
> run
2+
> testIRPositions

0 commit comments

Comments
 (0)