Skip to content

Commit 7a6517f

Browse files
dkorpelclaude
andcommitted
Fix #17751 - q{} strings don't normalize CRLF to LF
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 23ae810 commit 7a6517f

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

changelog/dmd.qstring-crlf.dd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Token strings now normalize `\r\n` to `\n`
2+
3+
Token strings (`q{...}`) now normalize carriage return + line feed sequences to
4+
a single line feed, consistent with other string literal types.
5+
6+
```d
7+
const string s = q{a
8+
b}; // file saved with CRLF line endings
9+
static assert(s == "a\nb");
10+
```

compiler/src/dmd/lexer.d

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,9 +1953,16 @@ class Lexer
19531953
{
19541954
const length = p - 1 - pstart;
19551955
if (supportInterpolation)
1956-
result.appendInterpolatedPart(pstart[0 .. length]);
1956+
{
1957+
normalizeCRLF(pstart[0 .. length]);
1958+
result.appendInterpolatedPart(stringbuffer[]);
1959+
}
19571960
else
1958-
result.setString(pstart[0 .. length]);
1961+
{
1962+
normalizeCRLF(pstart[0 .. length]);
1963+
result.setString(stringbuffer[]);
1964+
}
1965+
19591966
stringPostfix(result);
19601967
return;
19611968
}
@@ -1964,8 +1971,7 @@ class Lexer
19641971
if (!supportInterpolation)
19651972
goto default;
19661973

1967-
stringbuffer.setsize(0);
1968-
stringbuffer.write(pstart, p - 1 - pstart);
1974+
normalizeCRLF(pstart[0 .. p - 1 - pstart]);
19691975
if (!handleInterpolatedSegment(result, start))
19701976
goto default;
19711977

@@ -1984,6 +1990,23 @@ class Lexer
19841990
}
19851991
}
19861992

1993+
// Normalize CRLF to LF in raw source bytes and write into stringbuffer
1994+
private void normalizeCRLF(const(char)[] src)
1995+
{
1996+
stringbuffer.setsize(0);
1997+
foreach (i, char c; src)
1998+
{
1999+
if (c == '\r')
2000+
{
2001+
if (i + 1 < src.length && src[i + 1] == '\n')
2002+
continue;
2003+
stringbuffer.writeByte('\n');
2004+
}
2005+
else
2006+
stringbuffer.writeByte(c);
2007+
}
2008+
}
2009+
19872010
// returns true if it got special treatment as an interpolated segment
19882011
// otherwise returns false, indicating to treat it as just part of a normal string
19892012
private bool handleInterpolatedSegment(Token* token, Loc start)

compiler/test/unit/compilable/crlf.d

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ unittest
2424

2525
import support : compiles, stripDelimited;
2626

27-
// not using token string due to https://issues.dlang.org/show_bug.cgi?id=19315
2827
enum crLFCode = `
2928
#!/usr/bin/env dmd -run
3029
@@ -67,7 +66,11 @@ unittest
6766
`static assert(wstr == "foo\nbar\nbaz\n");`,
6867

6968
format!`enum dstr = q"(%s)";`("\r\nfoo\r\nbar\nbaz\r\n"),
70-
`static assert(dstr == "\nfoo\nbar\nbaz\n");`
69+
`static assert(dstr == "\nfoo\nbar\nbaz\n");`,
70+
71+
// https://github.com/dlang/dmd/issues/17751
72+
format!`enum tstr = q{%s};`("\r\nfoo\r\nbar\nbaz\r\n"),
73+
`static assert(tstr == "\nfoo\nbar\nbaz\n");`,
7174
];
7275

7376
enum code = crLFCode ~ "\r\n" ~ codeLines.join('\n') ~ '\n';

0 commit comments

Comments
 (0)