Skip to content

Commit

Permalink
Use correct position for error in escape
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Apr 13, 2024
1 parent b68ac48 commit a06f109
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 71 deletions.
1 change: 0 additions & 1 deletion src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1359,7 +1359,6 @@ self =>
def selector(start: Offset, t0: Tree): Tree = {
val t = stripParens(t0)
val point = if (isIdent) in.offset else in.lastOffset //scala/bug#8459
//assert(t.pos.isDefined, t)
if (t != EmptyTree)
Select(t, ident(skipIt = false)) setPos r2p(start, point, in.lastOffset)
else
Expand Down
11 changes: 7 additions & 4 deletions src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,22 @@ trait NamesDefaults { self: Analyzer =>

// never used for constructor calls, they always have a stable qualifier
def blockWithQualifier(qual: Tree, selected: Name) = {
val sym = blockTyper.context.owner.newValue(freshTermName(nme.QUAL_PREFIX)(typer.fresh), newFlags = ARTIFACT) setInfo uncheckedBounds(qual.tpe) setPos (qual.pos.makeTransparent)
val sym = blockTyper.context.owner.newValue(freshTermName(nme.QUAL_PREFIX)(typer.fresh), newFlags = ARTIFACT)
.setInfo(uncheckedBounds(qual.tpe))
.setPos(qual.pos.makeTransparent)
blockTyper.context.scope enter sym
val vd = atPos(sym.pos)(ValDef(sym, qual) setType NoType)
// it stays in Vegas: scala/bug#5720, scala/bug#5727
qual.changeOwner(blockTyper.context.owner, sym)
qual.changeOwner(blockTyper.context.owner, sym) // scala/bug#5720, scala/bug#5727

val newQual = atPos(qual.pos.focus)(blockTyper.typedQualifier(Ident(sym.name)))
val baseFunTransformed = atPos(baseFun.pos.makeTransparent) {
// setSymbol below is important because the 'selected' function might be overloaded. by
// assigning the correct method symbol, typedSelect will just assign the type. the reason
// to still call 'typed' is to correctly infer singleton types, scala/bug#5259.
val selectPos =
if(qual.pos.isRange && baseFun1.pos.isRange) qual.pos.union(baseFun1.pos).withStart(Math.min(qual.pos.end, baseFun1.pos.end))
if (qual.pos.isRange && baseFun1.pos.isRange)
if (qual.pos == baseFun1.pos) qual.pos
else qual.pos.union(baseFun1.pos).withStart(Math.min(qual.pos.end, baseFun1.pos.end)) // range that does not overlap
else baseFun1.pos
val f = blockTyper.typedOperator(Select(newQual, selected).setSymbol(baseFun1.symbol).setPos(selectPos))
if (funTargs.isEmpty) f
Expand Down
92 changes: 46 additions & 46 deletions src/compiler/scala/tools/reflect/FastStringInterpolator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
package scala.tools
package reflect

import scala.StringContext.{InvalidEscapeException, InvalidUnicodeEscapeException, processEscapes, processUnicode}
import scala.collection.mutable.ListBuffer
import scala.reflect.internal.util.Position
import nsc.Reporting.WarningCategory.Scala3Migration

trait FastStringInterpolator extends FormatInterpolator {
Expand All @@ -26,45 +29,46 @@ trait FastStringInterpolator extends FormatInterpolator {
// rewrite a tree like `scala.StringContext.apply("hello \\n ", " ", "").s("world", Test.this.foo)`
// to `"hello \n world ".+(Test.this.foo)`
private def interpolated(macroApp: Tree, isRaw: Boolean): Tree = macroApp match {
case Apply(Select(Apply(stringCtx@Select(qualSC, _), parts), _interpol), args) if
stringCtx.symbol == currentRun.runDefinitions.StringContext_apply &&
treeInfo.isQualifierSafeToElide(qualSC) &&
parts.forall(treeInfo.isLiteralString) &&
parts.length == (args.length + 1) =>
case Apply(Select(Apply(stringCtx @ Select(qualSC, _), parts), _interpol), args)
if stringCtx.symbol == currentRun.runDefinitions.StringContext_apply
&& treeInfo.isQualifierSafeToElide(qualSC)
&& parts.forall(treeInfo.isLiteralString)
&& parts.length == (args.length + 1) =>

val treated =
try
parts.mapConserve {
case lit @ Literal(Constant(stringVal: String)) =>
def asRaw = if (currentRun.sourceFeatures.unicodeEscapesRaw) stringVal else {
val processed = StringContext.processUnicode(stringVal)
if (processed == stringVal) stringVal else {
val pos = {
val diffindex = processed.zip(stringVal).zipWithIndex.collectFirst {
case ((p, o), i) if p != o => i
}.getOrElse(processed.length - 1)
lit.pos.withShift(diffindex)
}
def msg(fate: String) = s"Unicode escapes in raw interpolations are $fate; use literal characters instead"
if (currentRun.isScala3)
runReporting.warning(pos, msg("ignored in Scala 3 (or with -Xsource-features:unicode-escapes-raw)"), Scala3Migration, c.internal.enclosingOwner)
else
runReporting.deprecationWarning(pos, msg("deprecated"), "2.13.2", "", "")
processed
}
def adjustedEscPos(p: Position, delta: Int) = {
val start = p.start + delta
Position.range(p.source, start = start, point = start, end = start + 2)
}
val treated = parts.mapConserve {
case lit @ Literal(Constant(stringVal: String)) =>
def asRaw = if (currentRun.sourceFeatures.unicodeEscapesRaw) stringVal else {
val processed = processUnicode(stringVal)
if (processed == stringVal) stringVal else {
val pos = {
val diffindex = processed.zip(stringVal).zipWithIndex.collectFirst {
case ((p, o), i) if p != o => i
}.getOrElse(processed.length - 1)
lit.pos.withShift(diffindex)
}
val value =
if (isRaw) asRaw
else StringContext.processEscapes(stringVal)
val k = Constant(value)
// To avoid the backlash of backslash, taken literally by Literal, escapes are processed strictly (scala/bug#11196)
treeCopy.Literal(lit, k).setType(ConstantType(k))
case x => throw new MatchError(x)
def msg(fate: String) = s"Unicode escapes in raw interpolations are $fate; use literal characters instead"
if (currentRun.isScala3)
runReporting.warning(pos, msg("ignored in Scala 3 (or with -Xsource-features:unicode-escapes-raw)"), Scala3Migration, c.internal.enclosingOwner)
else
runReporting.deprecationWarning(pos, msg("deprecated"), "2.13.2", "", "")
processed
}
}
catch {
case ie: StringContext.InvalidEscapeException => c.abort(parts.head.pos.withShift(ie.index), ie.getMessage)
case iue: StringContext.InvalidUnicodeEscapeException => c.abort(parts.head.pos.withShift(iue.index), iue.getMessage)
}
val value =
try if (isRaw) asRaw else processEscapes(stringVal)
catch {
case ie: InvalidEscapeException => c.abort(adjustedEscPos(lit.pos, ie.index), ie.getMessage)
case iue: InvalidUnicodeEscapeException => c.abort(adjustedEscPos(lit.pos, iue.index), iue.getMessage)
}
val k = Constant(value)
// To avoid the backlash of backslash, taken literally by Literal, escapes are processed strictly (scala/bug#11196)
treeCopy.Literal(lit, k).setType(ConstantType(k))
case x => throw new MatchError(x)
}

if (args.forall(treeInfo.isLiteralString)) {
val it1 = treated.iterator
Expand All @@ -82,7 +86,7 @@ trait FastStringInterpolator extends FormatInterpolator {
else concatenate(treated, args)

// Fallback -- inline the original implementation of the `s` or `raw` interpolator.
case t@Apply(Select(someStringContext, _interpol), args) =>
case Apply(Select(someStringContext, _interpol), args) =>
q"""{
val sc = $someStringContext
_root_.scala.StringContext.standardInterpolator(
Expand All @@ -95,28 +99,25 @@ trait FastStringInterpolator extends FormatInterpolator {

def concatenate(parts: List[Tree], args: List[Tree]): Tree = {
val argsIndexed = args.toVector
val concatArgs = collection.mutable.ListBuffer[Tree]()
val concatArgs = ListBuffer.empty[Tree]
val numLits = parts.length
foreachWithIndex(parts.tail) { (lit, i) =>
val treatedContents = lit.asInstanceOf[Literal].value.stringValue
val emptyLit = treatedContents.isEmpty
if (i < numLits - 1) {
if (i < numLits - 1)
concatArgs += argsIndexed(i)
if (!emptyLit) concatArgs += lit
} else if (!emptyLit) {
concatArgs += lit
}
if (!emptyLit) concatArgs += lit
}
def mkConcat(pos: Position, lhs: Tree, rhs: Tree): Tree =
atPos(pos)(gen.mkMethodCall(gen.mkAttributedSelect(lhs, definitions.String_+), rhs :: Nil)).setType(definitions.StringTpe)

var result: Tree = parts.head
val chunkSize = 32
if (concatArgs.lengthCompare(chunkSize) <= 0) {
if (concatArgs.lengthCompare(chunkSize) <= 0)
concatArgs.foreach { t =>
result = mkConcat(t.pos, result, t)
}
} else {
else
concatArgs.toList.grouped(chunkSize).foreach {
case group =>
var chunkResult: Tree = Literal(Constant("")).setType(definitions.StringTpe)
Expand All @@ -125,7 +126,6 @@ trait FastStringInterpolator extends FormatInterpolator {
}
result = mkConcat(chunkResult.pos, result, chunkResult)
}
}

result
}
Expand Down
8 changes: 5 additions & 3 deletions src/reflect/scala/reflect/internal/util/Position.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ object Position {
final val tabInc = 8

private def validate[T <: Position](pos: T): T = {
if (pos.isRange)
assert(pos.start <= pos.end, s"bad position: ${pos.show}")

if (pos.isRange) {
import pos.{pos => _, _}
assert(start <= end, s"bad position: ${pos.show}")
//assert(start <= point && point < end, s"bad position: point $point out of range $start..$end: ${pos.show}")
}
pos
}

Expand Down
5 changes: 4 additions & 1 deletion test/files/neg/t8266-invalid-interp.check
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ t8266-invalid-interp.scala:8: error: \v is not supported, but for vertical tab u
invalid escape '\v' not one of [\b, \t, \n, \f, \r, \\, \", \', \uxxxx] at index 0 in "\v". Use \\ for literal \.
f"\v$x%.4s, Fred",
^
4 errors
t8266-invalid-interp.scala:11: error: invalid escape '\s' not one of [\b, \t, \n, \f, \r, \\, \", \', \uxxxx] at index 11 in ". And then \s.". Use \\ for literal \.
s"She said, $x. And then \s.",
^
5 errors
3 changes: 3 additions & 0 deletions test/files/neg/t8266-invalid-interp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ trait X {
f"a\vc",
f"\v$x%.4s, Fred",
)
def s = Seq(
s"She said, $x. And then \s.",
)
}
19 changes: 3 additions & 16 deletions test/files/run/t5603.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import scala.tools.partest._
import java.io._
import scala.tools.nsc._
import scala.tools.nsc.{Global, Settings, CompilerCommand}
import scala.tools.nsc.reporters.ConsoleReporter
import scala.tools.partest.DirectTest

object Test extends DirectTest {

override def extraSettings: String = "-usejavacp -Vprint:parser -Ystop-after:parser"
override def extraSettings: String = "-usejavacp -Vprint:parser -Vprint-pos -Yrangepos -Ystop-after:parser"

override def code = """
trait Greeting {
Expand All @@ -23,14 +19,5 @@ object Test extends DirectTest {
object Test extends App {}
""".trim

override def show(): Unit = compile()

override def newCompiler(args: String*): Global = {

val settings = new Settings()
settings.Xprintpos.value = true
settings.Yrangepos.value = true
val command = new CompilerCommand(tokenize(extraSettings) ++ args.toList, settings)
Global(command.settings, new ConsoleReporter(settings))
}
override def show(): Unit = assert(compile())
}

0 comments on commit a06f109

Please sign in to comment.