Merge pull request #8339 from roc-lang/dollars

Warn for `$` in the middle of idents
This commit is contained in:
Richard Feldman 2025-10-30 16:51:43 -04:00 committed by GitHub
commit b8386e3e1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 170 additions and 56 deletions

View file

@ -618,7 +618,7 @@ fn assertNoParseProblems(self: *TestEnv) !void {
defer report_buf.deinit();
for (self.parse_ast.tokenize_diagnostics.items) |tok_diag| {
var report = try self.parse_ast.tokenizeDiagnosticToReport(tok_diag, self.gpa);
var report = try self.parse_ast.tokenizeDiagnosticToReport(tok_diag, self.gpa, null);
defer report.deinit();
try renderReportToMarkdownBuffer(&report_buf, &report);

View file

@ -796,7 +796,7 @@ pub const BuildEnv = struct {
const module_name = file_abs;
for (ast.tokenize_diagnostics.items) |diagnostic| {
const report = try ast.tokenizeDiagnosticToReport(diagnostic, self.gpa);
const report = try ast.tokenizeDiagnosticToReport(diagnostic, self.gpa, file_abs);
self.sink.emitReport(pkg_name, module_name, report);
}
for (ast.parse_diagnostics.items) |diagnostic| {

View file

@ -574,7 +574,7 @@ pub const PackageEnv = struct {
// Convert parse diagnostics to reports
for (parse_ast.tokenize_diagnostics.items) |diagnostic| {
const report = try parse_ast.tokenizeDiagnosticToReport(diagnostic, self.gpa);
const report = try parse_ast.tokenizeDiagnosticToReport(diagnostic, self.gpa, st.path);
try st.reports.append(self.gpa, report);
}
for (parse_ast.parse_diagnostics.items) |diagnostic| {

View file

@ -116,7 +116,7 @@ pub fn deinit(self: *AST, gpa: std.mem.Allocator) void {
}
/// Convert a tokenize diagnostic to a Report for rendering
pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, allocator: std.mem.Allocator) !reporting.Report {
pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, allocator: std.mem.Allocator, filename: ?[]const u8) !reporting.Report {
const title = switch (diagnostic.tag) {
.MisplacedCarriageReturn => "MISPLACED CARRIAGE RETURN",
.AsciiControl => "ASCII CONTROL CHARACTER",
@ -127,6 +127,7 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
.UnclosedString => "UNCLOSED STRING",
.NonPrintableUnicodeInStrLiteral => "NON-PRINTABLE UNICODE IN STRING-LIKE LITERAL",
.InvalidUtf8InSource => "INVALID UTF-8",
.DollarInMiddleOfIdentifier => "STRAY DOLLAR SIGN",
};
const body = switch (diagnostic.tag) {
@ -139,6 +140,7 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
.UnclosedString => "This string is missing a closing quote.",
.NonPrintableUnicodeInStrLiteral => "Non-printable Unicode characters are not allowed in string-like literals.",
.InvalidUtf8InSource => "Invalid UTF-8 encoding found in source code. Roc source files must be valid UTF-8.",
.DollarInMiddleOfIdentifier => "Dollar sign ($) is only allowed at the very beginning of a name, not in the middle or at the end.",
};
var report = reporting.Report.init(allocator, title, .runtime_error);
@ -170,7 +172,7 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
try report.document.addSourceRegion(
region_info,
.error_highlight,
null, // No filename available for tokenize diagnostics
filename,
self.env.source,
env.line_starts.items.items,
);

View file

@ -484,6 +484,7 @@ pub const Diagnostic = struct {
UnclosedString,
NonPrintableUnicodeInStrLiteral,
InvalidUtf8InSource,
DollarInMiddleOfIdentifier,
};
};
@ -823,9 +824,16 @@ pub const Cursor = struct {
/// Returns whether the chomped identifier was valid - i.e. didn't contain any non-ascii characters.
pub fn chompIdentGeneral(self: *Cursor) bool {
var valid = true;
const start_pos = self.pos;
while (self.pos < self.buf.len) {
const c = self.buf[self.pos];
if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or c == '_' or c == '!') {
if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or c == '_' or c == '!' or c == '$') {
// Allow $ as a valid identifier character
if (c == '$' and self.pos > start_pos) {
// But warn if it's not at the start (pos > start_pos means we've moved)
// Use pushMessage to specify the exact location of the $ character
self.pushMessage(.DollarInMiddleOfIdentifier, self.pos, self.pos + 1);
}
self.pos += 1;
} else if (c >= 0x80) {
valid = false;
@ -2476,3 +2484,89 @@ test "non-printable characters in string literal" {
try std.testing.expect(messages.len == 0);
}
}
test "dollar sign in middle of identifier" {
const gpa = std.testing.allocator;
// Dollar sign in the middle of an identifier - foo$bar
{
const source = "foo$bar";
var diagnostics: [10]Diagnostic = undefined;
var env = try CommonEnv.init(gpa, try gpa.dupe(u8, ""));
defer env.deinit(gpa);
var tokenizer = try Tokenizer.init(&env, gpa, source, &diagnostics);
defer tokenizer.deinit(gpa);
try tokenizer.tokenize(gpa);
// Should have reported DollarInMiddleOfIdentifier
const messages = tokenizer.cursor.messages[0..tokenizer.cursor.message_count];
try std.testing.expect(messages.len > 0);
try std.testing.expectEqual(Diagnostic.Tag.DollarInMiddleOfIdentifier, messages[0].tag);
// Should tokenize as a valid LowerIdent (but with a warning)
const token_tags = tokenizer.output.tokens.items(.tag);
try std.testing.expect(token_tags.len >= 2); // At least the identifier and EOF
try std.testing.expectEqual(Token.Tag.LowerIdent, token_tags[0]);
}
// Dollar sign at the end of an identifier - foo$
{
const source = "foo$";
var diagnostics: [10]Diagnostic = undefined;
var env = try CommonEnv.init(gpa, try gpa.dupe(u8, ""));
defer env.deinit(gpa);
var tokenizer = try Tokenizer.init(&env, gpa, source, &diagnostics);
defer tokenizer.deinit(gpa);
try tokenizer.tokenize(gpa);
// Should have reported DollarInMiddleOfIdentifier
const messages = tokenizer.cursor.messages[0..tokenizer.cursor.message_count];
try std.testing.expect(messages.len > 0);
try std.testing.expectEqual(Diagnostic.Tag.DollarInMiddleOfIdentifier, messages[0].tag);
}
// Multiple dollar signs in identifier - foo$bar$baz
{
const source = "foo$bar$baz";
var diagnostics: [10]Diagnostic = undefined;
var env = try CommonEnv.init(gpa, try gpa.dupe(u8, ""));
defer env.deinit(gpa);
var tokenizer = try Tokenizer.init(&env, gpa, source, &diagnostics);
defer tokenizer.deinit(gpa);
try tokenizer.tokenize(gpa);
// Should have reported multiple DollarInMiddleOfIdentifier warnings
const messages = tokenizer.cursor.messages[0..tokenizer.cursor.message_count];
try std.testing.expect(messages.len >= 2);
try std.testing.expectEqual(Diagnostic.Tag.DollarInMiddleOfIdentifier, messages[0].tag);
try std.testing.expectEqual(Diagnostic.Tag.DollarInMiddleOfIdentifier, messages[1].tag);
}
// Dollar at the start is OK - no warning
{
const source = "$foo";
var diagnostics: [10]Diagnostic = undefined;
var env = try CommonEnv.init(gpa, try gpa.dupe(u8, ""));
defer env.deinit(gpa);
var tokenizer = try Tokenizer.init(&env, gpa, source, &diagnostics);
defer tokenizer.deinit(gpa);
try tokenizer.tokenize(gpa);
// Should NOT have any warnings
const messages = tokenizer.cursor.messages[0..tokenizer.cursor.message_count];
try std.testing.expect(messages.len == 0);
// Should tokenize as a valid LowerIdent
const token_tags = tokenizer.output.tokens.items(.tag);
try std.testing.expect(token_tags.len >= 2);
try std.testing.expectEqual(Token.Tag.LowerIdent, token_tags[0]);
}
}

View file

@ -885,7 +885,7 @@ fn compileSource(source: []const u8) !CompilerStageData {
// Collect tokenize diagnostics with additional error handling
for (parse_ast.tokenize_diagnostics.items) |diagnostic| {
const report = parse_ast.tokenizeDiagnosticToReport(diagnostic, allocator) catch {
const report = parse_ast.tokenizeDiagnosticToReport(diagnostic, allocator, null) catch {
// Log the error and continue processing other diagnostics
// This prevents crashes on malformed diagnostics or empty input
continue;

View file

@ -479,7 +479,7 @@ fn generateAllReports(
// Generate tokenize reports
for (parse_ast.tokenize_diagnostics.items) |diagnostic| {
const report = parse_ast.tokenizeDiagnosticToReport(diagnostic, allocator) catch |err| {
const report = parse_ast.tokenizeDiagnosticToReport(diagnostic, allocator, snapshot_path) catch |err| {
std.debug.panic("Failed to create tokenize report for snapshot {s}: {s}", .{ snapshot_path, @errorName(err) });
};
try reports.append(report);

View file

@ -8,11 +8,12 @@ type=expr
"abc\u(zzzz)def"
~~~
# EXPECTED
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - unicode_not_hex.md:1:5:1:13
# PROBLEMS
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_not_hex.md:1:5:1:13:**
```roc
"abc\u(zzzz)def"
```

View file

@ -8,11 +8,12 @@ type=expr
"abc\qdef"
~~~
# EXPECTED
INVALID ESCAPE SEQUENCE - :0:0:0:0
INVALID ESCAPE SEQUENCE - weird_escape.md:1:5:1:7
# PROBLEMS
**INVALID ESCAPE SEQUENCE**
This escape sequence is not recognized.
**weird_escape.md:1:5:1:7:**
```roc
"abc\qdef"
```

View file

@ -8,7 +8,7 @@ type=file
= "te
~~~
# EXPECTED
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_003.md:1:3:1:6
PARSE ERROR - fuzz_crash_003.md:1:1:1:2
PARSE ERROR - fuzz_crash_003.md:1:3:1:4
PARSE ERROR - fuzz_crash_003.md:1:4:1:6
@ -18,6 +18,7 @@ MISSING MAIN! FUNCTION - fuzz_crash_003.md:1:1:1:6
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_003.md:1:3:1:6:**
```roc
= "te
```

View file

@ -13,7 +13,7 @@ foo =
"onmo %
~~~
# EXPECTED
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_009.md:6:5:6:12
PARSE ERROR - fuzz_crash_009.md:1:2:1:3
PARSE ERROR - fuzz_crash_009.md:1:3:1:4
PARSE ERROR - fuzz_crash_009.md:1:4:1:5
@ -24,6 +24,7 @@ MISSING MAIN! FUNCTION - fuzz_crash_009.md:1:2:6:12
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_009.md:6:5:6:12:**
```roc
"onmo %
```

View file

@ -13,7 +13,7 @@ foo =
~~~
# EXPECTED
ASCII CONTROL CHARACTER - :0:0:0:0
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_010.md:5:5:5:35
PARSE ERROR - fuzz_crash_010.md:1:2:1:3
PARSE ERROR - fuzz_crash_010.md:1:3:1:4
PARSE ERROR - fuzz_crash_010.md:1:4:1:5
@ -28,6 +28,7 @@ ASCII control characters are not allowed in Roc source code.
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_010.md:5:5:5:35:**
```roc
"on (string 'onmo %')))
```

View file

@ -10,7 +10,7 @@ Fli/main.roc" }
Pair(a, b+ : (
~~~
# EXPECTED
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_021.md:1:13:1:16
PARSE ERROR - fuzz_crash_021.md:1:4:1:5
PARSE ERROR - fuzz_crash_021.md:1:5:1:9
PARSE ERROR - fuzz_crash_021.md:1:9:1:13
@ -25,6 +25,7 @@ TYPE MODULE MISSING MATCHING TYPE - fuzz_crash_021.md:1:1:3:15
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_021.md:1:13:1:16:**
```roc
Fli/main.roc" }
```

View file

@ -167,7 +167,7 @@ expect {
~~~
# EXPECTED
LEADING ZERO - :0:0:0:0
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_027.md:118:8:118:22
PARSE ERROR - fuzz_crash_027.md:40:5:40:6
PARSE ERROR - fuzz_crash_027.md:40:7:40:8
PARSE ERROR - fuzz_crash_027.md:40:9:40:10
@ -239,6 +239,7 @@ Numbers cannot have leading zeros.
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_027.md:118:8:118:22:**
```roc
crash "Unreachtement
```

View file

@ -9,11 +9,12 @@ app[]{f:platform"",r:"
}
~~~
# EXPECTED
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_058.md:1:22:1:23
# PROBLEMS
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_058.md:1:22:1:23:**
```roc
app[]{f:platform"",r:"
```

View file

@ -9,7 +9,7 @@ type=snippet
}
~~~
# EXPECTED
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_060.md:1:2:1:3
PARSE ERROR - fuzz_crash_060.md:1:1:1:2
PARSE ERROR - fuzz_crash_060.md:1:2:1:3
PARSE ERROR - fuzz_crash_060.md:1:3:1:3
@ -19,6 +19,7 @@ PARSE ERROR - fuzz_crash_060.md:2:1:2:2
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_060.md:1:2:1:3:**
```roc
0"
```

View file

@ -9,7 +9,7 @@ platform"
requires{}{n:0[import S exposing[
~~~
# EXPECTED
UNCLOSED STRING - :0:0:0:0
UNCLOSED STRING - fuzz_crash_061.md:1:9:1:10
UNEXPECTED TOKEN IN TYPE ANNOTATION - fuzz_crash_061.md:2:14:2:15
PARSE ERROR - fuzz_crash_061.md:2:11:2:12
PARSE ERROR - fuzz_crash_061.md:2:16:2:22
@ -17,6 +17,7 @@ PARSE ERROR - fuzz_crash_061.md:2:16:2:22
**UNCLOSED STRING**
This string is missing a closing quote.
**fuzz_crash_061.md:1:9:1:10:**
```roc
platform"
```

View file

@ -15,6 +15,7 @@ type=expr
}
~~~
# EXPECTED
STRAY DOLLAR SIGN - record_different_fields_error.md:6:10:6:11
UNEXPECTED TOKEN IN TYPE ANNOTATION - record_different_fields_error.md:2:20:2:21
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_error.md:2:21:2:39
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_error.md:2:39:2:40
@ -48,7 +49,6 @@ UNDEFINED VARIABLE - record_different_fields_error.md:5:5:5:10
UNDEFINED VARIABLE - record_different_fields_error.md:5:11:5:15
UNRECOGNIZED SYNTAX - record_different_fields_error.md:5:15:5:16
UNRECOGNIZED SYNTAX - record_different_fields_error.md:5:24:5:25
UNDEFINED VARIABLE - record_different_fields_error.md:6:5:6:10
MALFORMED TYPE - record_different_fields_error.md:6:20:6:21
UNRECOGNIZED SYNTAX - record_different_fields_error.md:6:21:6:27
UNRECOGNIZED SYNTAX - record_different_fields_error.md:6:27:6:28
@ -58,12 +58,22 @@ UNRECOGNIZED SYNTAX - record_different_fields_error.md:7:10:7:17
UNRECOGNIZED SYNTAX - record_different_fields_error.md:7:17:7:18
UNRECOGNIZED SYNTAX - record_different_fields_error.md:7:30:7:31
UNUSED VARIABLE - record_different_fields_error.md:3:5:3:14
UNUSED VARIABLE - record_different_fields_error.md:6:10:6:21
UNUSED VARIABLE - record_different_fields_error.md:6:5:6:21
UNUSED VALUE - record_different_fields_error.md:4:5:4:15
UNUSED VALUE - record_different_fields_error.md:4:17:4:25
UNUSED VALUE - record_different_fields_error.md:5:17:5:24
UNUSED VALUE - record_different_fields_error.md:7:19:7:30
# PROBLEMS
**STRAY DOLLAR SIGN**
Dollar sign ($) is only allowed at the very beginning of a name, not in the middle or at the end.
**record_different_fields_error.md:6:10:6:11:**
```roc
field$special: "dollar",
```
^
**UNEXPECTED TOKEN IN TYPE ANNOTATION**
The token **"** is not expected in a type annotation.
Type annotations should contain types like _Str_, _Num a_, or _List U64_.
@ -425,17 +435,6 @@ I don't recognize this syntax.
This might be a syntax error, an unsupported language feature, or a typo.
**UNDEFINED VARIABLE**
Nothing is named `field` in this scope.
Is there an `import` or `exposing` missing up-top?
**record_different_fields_error.md:6:5:6:10:**
```roc
field$special: "dollar",
```
^^^^^
**MALFORMED TYPE**
This type annotation is malformed or contains invalid syntax.
@ -536,15 +535,15 @@ The unused variable is declared here:
**UNUSED VARIABLE**
Variable `$special` is not used anywhere in your code.
Variable `field$special` is not used anywhere in your code.
If you don't need this variable, prefix it with an underscore like `_$special` to suppress this warning.
If you don't need this variable, prefix it with an underscore like `_field$special` to suppress this warning.
The unused variable is declared here:
**record_different_fields_error.md:6:10:6:21:**
**record_different_fields_error.md:6:5:6:21:**
```roc
field$special: "dollar",
```
^^^^^^^^^^^
^^^^^^^^^^^^^^^^
**UNUSED VALUE**
@ -598,7 +597,7 @@ NamedUnderscore,OpColon,StringStart,StringPart,StringEnd,Comma,
LowerIdent,OpColon,StringStart,StringPart,StringEnd,Comma,
UpperIdent,OpColon,StringStart,StringPart,StringEnd,Comma,
LowerIdent,OpUnaryMinus,LowerIdent,OpColon,StringStart,StringPart,StringEnd,Comma,
LowerIdent,LowerIdent,OpColon,StringStart,StringPart,StringEnd,Comma,
LowerIdent,OpColon,StringStart,StringPart,StringEnd,Comma,
LowerIdent,OpaqueName,OpColon,StringStart,StringPart,StringEnd,Comma,
CloseCurly,
EndOfFile,
@ -629,8 +628,7 @@ EndOfFile,
(e-string
(e-string-part (raw "kebab")))
(e-malformed (reason "expr_unexpected_token"))
(e-ident (raw "field"))
(s-type-anno (name "$special")
(s-type-anno (name "field$special")
(ty-malformed (tag "ty_anno_unexpected_token")))
(e-malformed (reason "expr_unexpected_token"))
(e-malformed (reason "expr_unexpected_token"))
@ -656,8 +654,7 @@ EndOfFile,
-case
"kebab"
field
$special :
field$special :
field
"at symbol"
@ -706,10 +703,8 @@ EndOfFile,
(e-literal (string "kebab"))))
(s-expr
(e-runtime-error (tag "expr_not_canonicalized")))
(s-expr
(e-runtime-error (tag "ident_not_in_scope")))
(s-let
(p-assign (ident "$special"))
(p-assign (ident "field$special"))
(e-anno-only))
(s-expr
(e-runtime-error (tag "expr_not_canonicalized")))

View file

@ -20,13 +20,13 @@ x = (
"\
~~~
# EXPECTED
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID ESCAPE SEQUENCE - :0:0:0:0
UNCLOSED STRING - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - string.md:4:3:4:5
INVALID UNICODE ESCAPE SEQUENCE - string.md:5:3:5:5
INVALID UNICODE ESCAPE SEQUENCE - string.md:6:3:6:6
INVALID UNICODE ESCAPE SEQUENCE - string.md:7:3:7:7
INVALID UNICODE ESCAPE SEQUENCE - string.md:8:3:8:8
INVALID ESCAPE SEQUENCE - string.md:13:2:14:1
UNCLOSED STRING - string.md:13:1:13:3
PARSE ERROR - string.md:13:1:13:2
PARSE ERROR - string.md:13:2:13:3
PARSE ERROR - string.md:13:3:13:3
@ -34,6 +34,7 @@ PARSE ERROR - string.md:13:3:13:3
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**string.md:4:3:4:5:**
```roc
"\u",
```
@ -43,6 +44,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**string.md:5:3:5:5:**
```roc
"\u)",
```
@ -52,6 +54,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**string.md:6:3:6:6:**
```roc
"\u(",
```
@ -61,6 +64,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**string.md:7:3:7:7:**
```roc
"\u()",
```
@ -70,6 +74,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**string.md:8:3:8:8:**
```roc
"\u(K)",
```
@ -79,6 +84,7 @@ This Unicode escape sequence is not valid.
**INVALID ESCAPE SEQUENCE**
This escape sequence is not recognized.
**string.md:13:2:14:1:**
```roc
"\
@ -88,6 +94,7 @@ This escape sequence is not recognized.
**UNCLOSED STRING**
This string is missing a closing quote.
**string.md:13:1:13:3:**
```roc
"\
```

View file

@ -28,12 +28,12 @@ y = 'u
'\
~~~
# EXPECTED
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - :0:0:0:0
INVALID ESCAPE SEQUENCE - :0:0:0:0
INVALID UNICODE ESCAPE SEQUENCE - unicode_single_quotes.md:5:6:5:8
INVALID UNICODE ESCAPE SEQUENCE - unicode_single_quotes.md:6:6:6:8
INVALID UNICODE ESCAPE SEQUENCE - unicode_single_quotes.md:7:6:7:9
INVALID UNICODE ESCAPE SEQUENCE - unicode_single_quotes.md:8:6:8:10
INVALID UNICODE ESCAPE SEQUENCE - unicode_single_quotes.md:10:6:10:11
INVALID ESCAPE SEQUENCE - unicode_single_quotes.md:21:2:22:1
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:5:5:5:9
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:6:5:6:10
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:7:5:7:10
@ -57,6 +57,7 @@ UNRECOGNIZED SYNTAX - unicode_single_quotes.md:18:5:18:7
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_single_quotes.md:5:6:5:8:**
```roc
'\u',
```
@ -66,6 +67,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_single_quotes.md:6:6:6:8:**
```roc
'\u)',
```
@ -75,6 +77,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_single_quotes.md:7:6:7:9:**
```roc
'\u(',
```
@ -84,6 +87,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_single_quotes.md:8:6:8:10:**
```roc
'\u()',
```
@ -93,6 +97,7 @@ This Unicode escape sequence is not valid.
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_single_quotes.md:10:6:10:11:**
```roc
'\u(K)',
```
@ -102,6 +107,7 @@ This Unicode escape sequence is not valid.
**INVALID ESCAPE SEQUENCE**
This escape sequence is not recognized.
**unicode_single_quotes.md:21:2:22:1:**
```roc
'\