Skip to content

Commit b687fcc

Browse files
committed
.
1 parent 5c8c892 commit b687fcc

File tree

1 file changed

+45
-58
lines changed

1 file changed

+45
-58
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 45 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,28 +1358,11 @@ object Parsers {
13581358
* @param offset The source offset where the string literal begins
13591359
* @return The dedented string, or str if errors were reported
13601360
*/
1361-
private def dedentString(str: String, offset: Offset): String = {
1362-
if (str.isEmpty) return str
1363-
1364-
// Find the last line (should be just whitespace before closing delimiter)
1365-
val lastNewlineIdx = str.lastIndexOf('\n')
1366-
1367-
if (lastNewlineIdx < 0) {
1368-
syntaxError(
1369-
em"dedented string literal must start with newline after opening quotes",
1370-
offset
1371-
)
1372-
return str
1373-
}
1374-
1375-
val closingIndent = str.substring(lastNewlineIdx + 1)
1376-
if (!closingIndent.forall(_.isWhitespace)) {
1377-
syntaxError(
1378-
em"last line of dedented string literal must contain only whitespace before closing delimiter",
1379-
offset
1380-
)
1381-
return str
1382-
}
1361+
private def dedentString(str: String,
1362+
offset: Offset,
1363+
closingIndent: String,
1364+
isFirstPart: Boolean,
1365+
isLastPart: Boolean): String = {
13831366

13841367
// Check for mixed tabs and spaces in closing indent
13851368
val hasTabs = closingIndent.contains('\t')
@@ -1395,10 +1378,8 @@ object Parsers {
13951378
// Split into lines
13961379
val linesAndWithSeps = (str.linesIterator.zip(str.linesWithSeparators)).toSeq
13971380

1398-
// Process all lines except the first (which is empty before the first newline)
1399-
// and the last (which is just the closing indentation)
14001381
var lineOffset = offset
1401-
val dedented = linesAndWithSeps.drop(1).dropRight(1).map { case (line, lineWithSep) =>
1382+
def dedentLine(line: String, lineWithSep: String) = {
14021383
val result =
14031384
if (line.startsWith(closingIndent)) line.substring(closingIndent.length)
14041385
else if (line.trim.isEmpty) "" // Empty or whitespace-only lines
@@ -1424,7 +1405,30 @@ object Parsers {
14241405
result
14251406
}
14261407

1427-
dedented.mkString("\n")
1408+
// If this is the first part of a string, then the first line is the empty string following
1409+
// the opening `'''` delimiter, so we skip it. If not, then the first line is immediately
1410+
// following an interpolated value, and should be used raw without indenting
1411+
val firstLine =
1412+
if (isFirstPart) Nil
1413+
else {
1414+
val (line, lineWithSep) = linesAndWithSeps.head
1415+
lineOffset += lineWithSep.length
1416+
Seq(line)
1417+
}
1418+
1419+
// Process all lines except the first and last, which require special handling
1420+
val dedented = linesAndWithSeps.drop(1).dropRight(1).map { case (line, lineWithSep) =>
1421+
dedentLine(line, lineWithSep)
1422+
}
1423+
1424+
// If this is the last part of the string, then the last line is the indentation-only
1425+
// line preceding the closing delimiter, and should be ignored. If not, then the last line
1426+
// also needs to be de-dented
1427+
val lastLine =
1428+
if (isLastPart) Nil
1429+
else Seq(dedentLine(linesAndWithSeps.last._1, linesAndWithSeps.last._2))
1430+
1431+
(firstLine ++ dedented ++ lastLine).mkString("\n")
14281432
}
14291433

14301434
/** Literal ::= SimpleLiteral
@@ -1463,7 +1467,7 @@ object Parsers {
14631467
// For non-interpolated dedented strings, check if the token starts with '''
14641468
val str = in.strVal
14651469
if (token == STRINGLIT && !inStringInterpolation && isDedentedStringLiteral(negOffset)) {
1466-
dedentString(str, negOffset)
1470+
dedentString(str, negOffset, extractClosingIndent(str, negOffset), true, true)
14671471
} else str
14681472
case TRUE => true
14691473
case FALSE => false
@@ -1545,14 +1549,11 @@ object Parsers {
15451549
in.buf(in.charOffset + 1) == '"'
15461550
val isDedented =
15471551
in.charOffset + 2 < in.buf.length &&
1552+
in.buf(in.charOffset - 1) == '\'' &&
15481553
in.buf(in.charOffset) == '\'' &&
1549-
in.buf(in.charOffset + 1) == '\'' &&
1550-
in.buf(in.charOffset + 2) == '\''
1551-
1554+
in.buf(in.charOffset + 1) == '\''
15521555
in.nextToken()
15531556

1554-
// For dedented strings, we need to collect all string parts first,
1555-
// then dedent them all based on the closing indentation
15561557
if (isDedented) {
15571558
// Collect all string parts and their offsets
15581559
val stringParts = new ListBuffer[(String, Offset)]
@@ -1599,11 +1600,11 @@ object Parsers {
15991600
// Now dedent all string parts based on the last one's closing indentation
16001601
if (stringParts.nonEmpty) {
16011602
val lastPart = stringParts.last._1
1602-
val closingIndent = extractClosingIndent(lastPart)
1603+
val closingIndent = extractClosingIndent(lastPart, in.offset)
16031604

16041605
// Dedent all parts
1605-
val dedentedParts = stringParts.map { case (str, offset) =>
1606-
(dedentStringPart(str, closingIndent), offset)
1606+
val dedentedParts = stringParts.zipWithIndex.map { case ((str, offset), index) =>
1607+
(dedentString(str, in.offset, closingIndent, index == 0, index == stringParts.length-1), offset)
16071608
}
16081609

16091610
// Build the segments with dedented strings
@@ -1658,30 +1659,16 @@ object Parsers {
16581659
}
16591660

16601661
/** Extract the closing indentation from the last line of a string */
1661-
private def extractClosingIndent(str: String): String = {
1662-
val lastNewlineIdx = str.lastIndexOf('\n')
1663-
if (lastNewlineIdx < 0) "" else str.substring(lastNewlineIdx + 1)
1664-
}
1665-
1666-
/** Dedent a string part by removing the specified indentation from each line */
1667-
private def dedentStringPart(str: String, closingIndent: String): String = {
1668-
if (str.isEmpty || closingIndent.isEmpty) return str
1669-
1670-
val lines = str.split("\n", -1) // -1 to keep trailing empty strings
1671-
1672-
val dedented = lines.map { line =>
1673-
if (line.startsWith(closingIndent)) {
1674-
line.substring(closingIndent.length)
1675-
} else if (line.trim.isEmpty) {
1676-
// Empty or whitespace-only lines
1677-
""
1678-
} else {
1679-
// Line doesn't start with the closing indentation, keep as-is
1680-
line
1681-
}
1662+
private def extractClosingIndent(str: String, offset: Offset): String = {
1663+
val closingIndent = str.linesIterator.toSeq.last
1664+
if (!closingIndent.forall(_.isWhitespace)) {
1665+
syntaxError(
1666+
em"last line of dedented string literal must contain only whitespace before closing delimiter",
1667+
offset
1668+
)
1669+
return str
16821670
}
1683-
1684-
dedented.mkString("\n")
1671+
closingIndent
16851672
}
16861673

16871674
/* ------------- NEW LINES ------------------------------------------------- */

0 commit comments

Comments
 (0)