Change the handling of invalid single quote to improve error messages. (#8370)

* add value_not_exposed report

* Change handling of invalid single-quote.

* fix typo
This commit is contained in:
Fabian Schmalzried 2025-11-11 13:42:35 +01:00 committed by GitHub
parent 856b909a57
commit a55b048dad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 242 additions and 225 deletions

View file

@ -2943,7 +2943,8 @@ fn canonicalizeDeclWithAnnotation(
fn parseSingleQuoteCodepoint(
inner_text: []const u8,
) ?u21 {
) u21 {
// tokenizer checks for valid single quote codepoints, so every error case is unreachable here
const escaped = inner_text[0] == '\\';
if (escaped) {
@ -2951,13 +2952,9 @@ fn parseSingleQuoteCodepoint(
switch (c) {
'u' => {
const hex_code = inner_text[3 .. inner_text.len - 1];
const codepoint = std.fmt.parseInt(u21, hex_code, 16) catch {
return null;
};
const codepoint = std.fmt.parseInt(u21, hex_code, 16) catch unreachable;
if (!std.unicode.utf8ValidCodepoint(codepoint)) {
return null;
}
std.debug.assert(std.unicode.utf8ValidCodepoint(codepoint));
return codepoint;
},
@ -2973,26 +2970,16 @@ fn parseSingleQuoteCodepoint(
't' => {
return '\t';
},
else => {
return null;
},
else => unreachable,
}
} else {
const view = std.unicode.Utf8View.init(inner_text) catch |err| switch (err) {
error.InvalidUtf8 => {
return null;
},
};
const view = std.unicode.Utf8View.init(inner_text) catch unreachable;
var iterator = view.iterator();
if (iterator.nextCodepoint()) |codepoint| {
std.debug.assert(iterator.nextCodepoint() == null);
return codepoint;
} else {
// only single valid utf8 codepoint can be here after tokenization
unreachable;
}
const codepoint = iterator.nextCodepoint().?;
std.debug.assert(iterator.nextCodepoint() == null);
return codepoint;
}
}
@ -3034,34 +3021,30 @@ fn canonicalizeSingleQuote(
// Resolve to a string slice from the source
const token_text = self.parse_ir.resolve(token);
std.debug.assert(token_text[0] == '\'' and token_text[token_text.len - 1] == '\'');
if (parseSingleQuoteCodepoint(token_text[1 .. token_text.len - 1])) |codepoint| {
const value_content = CIR.IntValue{
.bytes = @bitCast(@as(u128, @intCast(codepoint))),
.kind = .u128,
};
if (comptime Idx == Expr.Idx) {
const expr_idx = try self.env.addExpr(CIR.Expr{
.e_num = .{
.value = value_content,
.kind = .int_unbound,
},
}, region);
return expr_idx;
} else if (comptime Idx == Pattern.Idx) {
const pat_idx = try self.env.addPattern(Pattern{ .num_literal = .{
const codepoint = parseSingleQuoteCodepoint(token_text[1 .. token_text.len - 1]);
const value_content = CIR.IntValue{
.bytes = @bitCast(@as(u128, @intCast(codepoint))),
.kind = .u128,
};
if (comptime Idx == Expr.Idx) {
const expr_idx = try self.env.addExpr(CIR.Expr{
.e_num = .{
.value = value_content,
.kind = .int_unbound,
} }, region);
return pat_idx;
} else {
@compileError("Unsupported Idx type");
}
},
}, region);
return expr_idx;
} else if (comptime Idx == Pattern.Idx) {
const pat_idx = try self.env.addPattern(Pattern{ .num_literal = .{
.value = value_content,
.kind = .int_unbound,
} }, region);
return pat_idx;
} else {
@compileError("Unsupported Idx type");
}
return try self.env.pushMalformed(Idx, Diagnostic{ .invalid_single_quote = .{
.region = region,
} });
}
fn canonicalizeRecordField(

View file

@ -30,9 +30,6 @@ pub const Diagnostic = union(enum) {
invalid_num_literal: struct {
region: Region,
},
invalid_single_quote: struct {
region: Region,
},
empty_tuple: struct {
region: Region,
},

View file

@ -954,7 +954,7 @@ pub fn diagnosticToReport(self: *Self, diagnostic: CIR.Diagnostic, allocator: st
// Format the message to match origin/main
try report.document.addText("The type ");
try report.document.addInlineCode(type_name);
try report.document.addReflowingText(" is not an exposed by the module ");
try report.document.addReflowingText(" is not exposed by the module ");
try report.document.addInlineCode(module_name);
try report.document.addReflowingText(".");
try report.document.addLineBreak();
@ -973,6 +973,33 @@ pub fn diagnosticToReport(self: *Self, diagnostic: CIR.Diagnostic, allocator: st
break :blk report;
},
.value_not_exposed => |data| blk: {
const region_info = self.calcRegionInfo(data.region);
var report = Report.init(allocator, "VALUE NOT EXPOSED", .runtime_error);
// Format the message to match origin/main
try report.document.addText("The value ");
try report.document.addInlineCode(self.getIdent(data.value_name));
try report.document.addReflowingText(" is not exposed by the module ");
try report.document.addInlineCode(self.getIdent(data.module_name));
try report.document.addReflowingText(".");
try report.document.addLineBreak();
try report.document.addLineBreak();
try report.document.addReflowingText("You're attempting to use this value here:");
try report.document.addLineBreak();
const owned_filename = try report.addOwnedString(filename);
try report.document.addSourceRegion(
region_info,
.error_highlight,
owned_filename,
self.getSourceAll(),
self.getLineStartsAll(),
);
break :blk report;
},
.module_not_found => |data| blk: {
const region_info = self.calcRegionInfo(data.region);

View file

@ -151,7 +151,6 @@ pub const Tag = enum {
// diagnostic indices stored in malformed nodes.
diag_not_implemented,
diag_invalid_num_literal,
diag_invalid_single_quote,
diag_empty_single_quote,
diag_empty_tuple,
diag_ident_already_in_scope,

View file

@ -128,7 +128,7 @@ pub fn deinit(store: *NodeStore) void {
/// when adding/removing variants from ModuleEnv unions. Update these when modifying the unions.
///
/// Count of the diagnostic nodes in the ModuleEnv
pub const MODULEENV_DIAGNOSTIC_NODE_COUNT = 59;
pub const MODULEENV_DIAGNOSTIC_NODE_COUNT = 58;
/// Count of the expression nodes in the ModuleEnv
pub const MODULEENV_EXPR_NODE_COUNT = 35;
/// Count of the statement nodes in the ModuleEnv
@ -2670,10 +2670,6 @@ pub fn addDiagnostic(store: *NodeStore, reason: CIR.Diagnostic) Allocator.Error!
node.tag = .diag_invalid_num_literal;
region = r.region;
},
.invalid_single_quote => |r| {
node.tag = .diag_invalid_single_quote;
region = r.region;
},
.empty_tuple => |r| {
node.tag = .diag_empty_tuple;
region = r.region;
@ -3029,9 +3025,6 @@ pub fn getDiagnostic(store: *const NodeStore, diagnostic: CIR.Diagnostic.Idx) CI
.diag_invalid_num_literal => return CIR.Diagnostic{ .invalid_num_literal = .{
.region = store.getRegionAt(node_idx),
} },
.diag_invalid_single_quote => return CIR.Diagnostic{ .invalid_single_quote = .{
.region = store.getRegionAt(node_idx),
} },
.diag_empty_tuple => return CIR.Diagnostic{ .empty_tuple = .{
.region = store.getRegionAt(node_idx),
} },

View file

@ -686,12 +686,6 @@ test "NodeStore round trip - Diagnostics" {
},
});
try diagnostics.append(gpa, CIR.Diagnostic{
.invalid_single_quote = .{
.region = rand_region(),
},
});
try diagnostics.append(gpa, CIR.Diagnostic{
.f64_pattern_literal = .{
.region = rand_region(),

View file

@ -142,6 +142,9 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
.NonPrintableUnicodeInStrLiteral => "NON-PRINTABLE UNICODE IN STRING-LIKE LITERAL",
.InvalidUtf8InSource => "INVALID UTF-8",
.DollarInMiddleOfIdentifier => "STRAY DOLLAR SIGN",
.SingleQuoteTooLong => "SINGLE QUOTE TOO LONG",
.SingleQuoteEmpty => "SINGLE QUOTE EMPTY",
.SingleQuoteUnclosed => "UNCLOSED SINGLE QUOTE",
};
const body = switch (diagnostic.tag) {
@ -155,6 +158,8 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
.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.",
.SingleQuoteTooLong, .SingleQuoteEmpty => "Single-quoted literals must contain exactly one valid UTF-8 codepoint.",
.SingleQuoteUnclosed => "This single-quoted literal is missing a closing quote.",
};
var report = reporting.Report.init(allocator, title, .runtime_error);

View file

@ -50,10 +50,7 @@ pub const Token = struct {
StringPart,
MalformedStringPart, // malformed, but should be treated similar to a StringPart in the parser
SingleQuote,
MalformedSingleQuoteUnclosed, // malformed, but should be treated similar to a SingleQuote in the parser
MalformedSingleQuoteEmpty, // malformed, but should be treated similar to a SingleQuote in the parser
MalformedSingleQuoteTooLong, // malformed, but should be treated similar to a SingleQuote in the parser
MalformedSingleQuoteInvalidEscapeSequence, // malformed, but should be treated similar to a SingleQuote in the parser
MalformedSingleQuote, // malformed, but should be treated similar to a SingleQuote in the parser
Int,
MalformedNumberBadSuffix, // malformed, but should be treated similar to an int in the parser
MalformedNumberUnicodeSuffix, // malformed, but should be treated similar to an int in the parser
@ -315,10 +312,7 @@ pub const Token = struct {
.MalformedOpaqueNameWithoutName,
.MalformedUnicodeIdent,
.MalformedUnknownToken,
.MalformedSingleQuoteUnclosed,
.MalformedSingleQuoteEmpty,
.MalformedSingleQuoteTooLong,
.MalformedSingleQuoteInvalidEscapeSequence,
.MalformedSingleQuote,
.MalformedStringPart,
=> true,
};
@ -486,6 +480,9 @@ pub const Diagnostic = struct {
NonPrintableUnicodeInStrLiteral,
InvalidUtf8InSource,
DollarInMiddleOfIdentifier,
SingleQuoteTooLong,
SingleQuoteEmpty,
SingleQuoteUnclosed,
};
};
@ -924,6 +921,16 @@ pub const Cursor = struct {
return error.InvalidUnicodeEscapeSequence;
}
}
const hex_code = self.buf[hex_start .. self.pos - 1];
const codepoint = std.fmt.parseInt(u21, hex_code, 16) catch {
self.pushMessage(.InvalidUnicodeEscapeSequence, escape_start, self.pos);
return error.InvalidUnicodeEscapeSequence;
};
if (!std.unicode.utf8ValidCodepoint(codepoint)) {
self.pushMessage(.InvalidUnicodeEscapeSequence, escape_start, self.pos);
return error.InvalidUnicodeEscapeSequence;
}
},
else => {
// Include the character after the backslash in the error region
@ -941,7 +948,9 @@ pub const Cursor = struct {
TooLong,
Invalid,
};
std.debug.assert(self.peek() == '\'');
const start = self.pos;
// Skip the initial quote.
self.pos += 1;
@ -959,7 +968,8 @@ pub const Cursor = struct {
switch (state) {
.Empty => switch (c) {
'\'' => {
return .MalformedSingleQuoteEmpty;
self.pushMessage(.SingleQuoteEmpty, start, self.pos);
return .MalformedSingleQuote;
},
'\\' => {
state = .Enough;
@ -983,20 +993,22 @@ pub const Cursor = struct {
},
.TooLong => switch (c) {
'\'' => {
return .MalformedSingleQuoteTooLong;
self.pushMessage(.SingleQuoteTooLong, start, self.pos);
return .MalformedSingleQuote;
},
else => {},
},
.Invalid => switch (c) {
'\'' => {
return .MalformedSingleQuoteInvalidEscapeSequence;
return .MalformedSingleQuote;
},
else => {},
},
}
}
return .MalformedSingleQuoteUnclosed;
self.pushMessage(.SingleQuoteUnclosed, start, self.pos);
return .MalformedSingleQuote;
}
/// Chomps a UTF-8 codepoint and advances the cursor position.
@ -2277,10 +2289,7 @@ fn rebuildBufferForTesting(buf: []const u8, tokens: *TokenizedBuffer, alloc: std
.MalformedNamedUnderscoreUnicode,
.MalformedOpaqueNameUnicode,
.MalformedOpaqueNameWithoutName,
.MalformedSingleQuoteEmpty,
.MalformedSingleQuoteTooLong,
.MalformedSingleQuoteUnclosed,
.MalformedSingleQuoteInvalidEscapeSequence,
.MalformedSingleQuote,
.MalformedStringPart,
=> {
return error.Unsupported;

View file

@ -8,27 +8,34 @@ type=expr
"\u(FFFFFF)"
~~~
# EXPECTED
NIL
INVALID UNICODE ESCAPE SEQUENCE - unicode_overflow_str.md:1:2:1:12
# PROBLEMS
NIL
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_overflow_str.md:1:2:1:12:**
```roc
"\u(FFFFFF)"
```
^^^^^^^^^^
# TOKENS
~~~zig
StringStart,StringPart,StringEnd,
StringStart,MalformedStringPart,StringEnd,
EndOfFile,
~~~
# PARSE
~~~clojure
(e-string
(e-string-part (raw "\u(FFFFFF)")))
(e-string)
~~~
# FORMATTED
~~~roc
NO CHANGE
""
~~~
# CANONICALIZE
~~~clojure
(e-string
(e-literal (string "\u(FFFFFF)")))
(e-string)
~~~
# TYPES
~~~clojure

View file

@ -11,6 +11,7 @@ mule []
vavar t= '
~~~
# EXPECTED
UNCLOSED SINGLE QUOTE - fuzz_crash_031.md:4:10:4:11
PARSE ERROR - fuzz_crash_031.md:1:1:1:5
PARSE ERROR - fuzz_crash_031.md:1:6:1:7
PARSE ERROR - fuzz_crash_031.md:1:7:1:8
@ -19,6 +20,16 @@ UNEXPECTED TOKEN IN EXPRESSION - fuzz_crash_031.md:4:10:4:11
UNRECOGNIZED SYNTAX - fuzz_crash_031.md:4:10:4:11
MISSING MAIN! FUNCTION - fuzz_crash_031.md:1:1:4:11
# PROBLEMS
**UNCLOSED SINGLE QUOTE**
This single-quoted literal is missing a closing quote.
**fuzz_crash_031.md:4:10:4:11:**
```roc
vavar t= '
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
@ -104,7 +115,7 @@ vavar t= '
# TOKENS
~~~zig
LowerIdent,OpenSquare,CloseSquare,
LowerIdent,LowerIdent,OpAssign,MalformedSingleQuoteUnclosed,
LowerIdent,LowerIdent,OpAssign,MalformedSingleQuote,
EndOfFile,
~~~
# PARSE

View file

@ -9,9 +9,20 @@ module[}('
)
~~~
# EXPECTED
UNCLOSED SINGLE QUOTE - fuzz_crash_039.md:1:10:1:11
PARSE ERROR - fuzz_crash_039.md:1:8:1:9
PARSE ERROR - fuzz_crash_039.md:3:1:3:1
# PROBLEMS
**UNCLOSED SINGLE QUOTE**
This single-quoted literal is missing a closing quote.
**fuzz_crash_039.md:1:10:1:11:**
```roc
module[}('
```
^
**PARSE ERROR**
A parsing error occurred: `exposed_item_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
@ -36,7 +47,7 @@ This is an unexpected parsing error. Please check your syntax.
# TOKENS
~~~zig
KwModule,OpenSquare,CloseCurly,NoSpaceOpenRound,MalformedSingleQuoteUnclosed,
KwModule,OpenSquare,CloseCurly,NoSpaceOpenRound,MalformedSingleQuote,
CloseRound,
EndOfFile,
~~~

View file

@ -14,6 +14,7 @@ x = (
'\u(',
'\u()',
'\u(1F680)',
'\u(EDA0B5)'
'\u(K)',
'\\',
'\'',
@ -32,27 +33,24 @@ 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
INVALID UNICODE ESCAPE SEQUENCE - unicode_single_quotes.md:10:6:10:16
INVALID UNICODE ESCAPE SEQUENCE - unicode_single_quotes.md:11:6:11:11
SINGLE QUOTE EMPTY - unicode_single_quotes.md:14:5:14:7
SINGLE QUOTE TOO LONG - unicode_single_quotes.md:15:5:15:11
UNCLOSED SINGLE QUOTE - unicode_single_quotes.md:16:5:16:9
UNCLOSED SINGLE QUOTE - unicode_single_quotes.md:19:5:19:7
INVALID ESCAPE SEQUENCE - unicode_single_quotes.md:22:2:23:1
UNCLOSED SINGLE QUOTE - unicode_single_quotes.md:22:1:22:3
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
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:8:5:8:11
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:10:5:10:12
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:13:5:13:7
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:14:5:14:11
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:15:5:15:9
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:18:5:18:7
PARSE ERROR - unicode_single_quotes.md:21:1:21:3
INVALID TUPLE ELEMENT - :0:0:0:0
INVALID TUPLE ELEMENT - :0:0:0:0
INVALID TUPLE ELEMENT - :0:0:0:0
INVALID TUPLE ELEMENT - :0:0:0:0
INVALID TUPLE ELEMENT - :0:0:0:0
INVALID TUPLE ELEMENT - :0:0:0:0
INVALID TUPLE ELEMENT - :0:0:0:0
INVALID TUPLE ELEMENT - :0:0:0:0
UNRECOGNIZED SYNTAX - unicode_single_quotes.md:18:5:18:7
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:10:5:10:17
PARSE ERROR - unicode_single_quotes.md:17:1:17:2
UNEXPECTED TOKEN IN EXPRESSION - unicode_single_quotes.md:19:5:19:7
PARSE ERROR - unicode_single_quotes.md:22:1:22:3
UNRECOGNIZED SYNTAX - unicode_single_quotes.md:17:1:17:2
UNRECOGNIZED SYNTAX - unicode_single_quotes.md:19:5:19:7
# PROBLEMS
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
@ -97,23 +95,83 @@ 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:**
**unicode_single_quotes.md:10:6:10:16:**
```roc
'\u(EDA0B5)'
```
^^^^^^^^^^
**INVALID UNICODE ESCAPE SEQUENCE**
This Unicode escape sequence is not valid.
**unicode_single_quotes.md:11:6:11:11:**
```roc
'\u(K)',
```
^^^^^
**SINGLE QUOTE EMPTY**
Single-quoted literals must contain exactly one valid UTF-8 codepoint.
**unicode_single_quotes.md:14:5:14:7:**
```roc
'',
```
^^
**SINGLE QUOTE TOO LONG**
Single-quoted literals must contain exactly one valid UTF-8 codepoint.
**unicode_single_quotes.md:15:5:15:11:**
```roc
'long',
```
^^^^^^
**UNCLOSED SINGLE QUOTE**
This single-quoted literal is missing a closing quote.
**unicode_single_quotes.md:16:5:16:9:**
```roc
'\',
```
^^^^
**UNCLOSED SINGLE QUOTE**
This single-quoted literal is missing a closing quote.
**unicode_single_quotes.md:19:5:19:7:**
```roc
y = 'u
```
^^
**INVALID ESCAPE SEQUENCE**
This escape sequence is not recognized.
**unicode_single_quotes.md:21:2:22:1:**
**unicode_single_quotes.md:22:2:23:1:**
```roc
'\
```
**UNCLOSED SINGLE QUOTE**
This single-quoted literal is missing a closing quote.
**unicode_single_quotes.md:22:1:22:3:**
```roc
'\
```
^^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **'\u'** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
@ -159,54 +217,32 @@ Expressions can be identifiers, literals, function calls, or operators.
**UNEXPECTED TOKEN IN EXPRESSION**
The token **'\u(K)'** is not expected in an expression.
The token **'\u(EDA0B5)'** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**unicode_single_quotes.md:10:5:10:12:**
**unicode_single_quotes.md:10:5:10:17:**
```roc
'\u(K)',
'\u(EDA0B5)'
```
^^^^^^^
^^^^^^^^^^^^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **''** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**PARSE ERROR**
A parsing error occurred: `expected_expr_close_round_or_comma`
This is an unexpected parsing error. Please check your syntax.
**unicode_single_quotes.md:13:5:13:7:**
**unicode_single_quotes.md:17:1:17:2:**
```roc
'',
)
```
^^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **'long'** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**unicode_single_quotes.md:14:5:14:11:**
```roc
'long',
```
^^^^^^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **'\',** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**unicode_single_quotes.md:15:5:15:9:**
```roc
'\',
```
^^^^
^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **'u** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**unicode_single_quotes.md:18:5:18:7:**
**unicode_single_quotes.md:19:5:19:7:**
```roc
y = 'u
```
@ -217,41 +253,28 @@ y = 'u
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
**unicode_single_quotes.md:21:1:21:3:**
**unicode_single_quotes.md:22:1:22:3:**
```roc
'\
```
^^
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
**unicode_single_quotes.md:17:1:17:2:**
```roc
)
```
^
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
**INVALID TUPLE ELEMENT**
This tuple element is malformed or contains invalid syntax.
This might be a syntax error, an unsupported language feature, or a typo.
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
**unicode_single_quotes.md:18:5:18:7:**
**unicode_single_quotes.md:19:5:19:7:**
```roc
y = 'u
```
@ -265,20 +288,21 @@ LowerIdent,OpAssign,OpenRound,
SingleQuote,Comma,
SingleQuote,Comma,
SingleQuote,Comma,
MalformedSingleQuoteInvalidEscapeSequence,Comma,
MalformedSingleQuoteInvalidEscapeSequence,Comma,
MalformedSingleQuoteInvalidEscapeSequence,Comma,
MalformedSingleQuoteInvalidEscapeSequence,Comma,
MalformedSingleQuote,Comma,
MalformedSingleQuote,Comma,
MalformedSingleQuote,Comma,
MalformedSingleQuote,Comma,
SingleQuote,Comma,
MalformedSingleQuoteInvalidEscapeSequence,Comma,
MalformedSingleQuote,
MalformedSingleQuote,Comma,
SingleQuote,Comma,
SingleQuote,Comma,
MalformedSingleQuoteEmpty,Comma,
MalformedSingleQuoteTooLong,Comma,
MalformedSingleQuoteUnclosed,
MalformedSingleQuote,Comma,
MalformedSingleQuote,Comma,
MalformedSingleQuote,
CloseRound,
LowerIdent,OpAssign,MalformedSingleQuoteUnclosed,
MalformedSingleQuoteUnclosed,
LowerIdent,OpAssign,MalformedSingleQuote,
MalformedSingleQuote,
EndOfFile,
~~~
# PARSE
@ -288,21 +312,7 @@ EndOfFile,
(statements
(s-decl
(p-ident (raw "x"))
(e-tuple
(e-single-quote (raw "'a'"))
(e-single-quote (raw "'é'"))
(e-single-quote (raw "'🚀'"))
(e-malformed (reason "expr_unexpected_token"))
(e-malformed (reason "expr_unexpected_token"))
(e-malformed (reason "expr_unexpected_token"))
(e-malformed (reason "expr_unexpected_token"))
(e-single-quote (raw "'\u(1F680)'"))
(e-malformed (reason "expr_unexpected_token"))
(e-single-quote (raw "'\\'"))
(e-single-quote (raw "'\''"))
(e-malformed (reason "expr_unexpected_token"))
(e-malformed (reason "expr_unexpected_token"))
(e-malformed (reason "expr_unexpected_token"))))
(e-malformed (reason "expected_expr_close_round_or_comma")))
(s-decl
(p-ident (raw "y"))
(e-malformed (reason "expr_unexpected_token")))
@ -310,22 +320,8 @@ EndOfFile,
~~~
# FORMATTED
~~~roc
x = (
'a',
'é',
'🚀',
,
,
,
,
'\u(1F680)',
,
'\\',
'\'',
,
,
,
)
x =
y =
@ -336,22 +332,7 @@ y =
(can-ir
(d-let
(p-assign (ident "x"))
(e-tuple
(elems
(e-num (value "97"))
(e-num (value "233"))
(e-num (value "128640"))
(e-runtime-error (tag "tuple_elem_not_canonicalized"))
(e-runtime-error (tag "tuple_elem_not_canonicalized"))
(e-runtime-error (tag "tuple_elem_not_canonicalized"))
(e-runtime-error (tag "tuple_elem_not_canonicalized"))
(e-num (value "128640"))
(e-runtime-error (tag "tuple_elem_not_canonicalized"))
(e-num (value "92"))
(e-num (value "39"))
(e-runtime-error (tag "tuple_elem_not_canonicalized"))
(e-runtime-error (tag "tuple_elem_not_canonicalized"))
(e-runtime-error (tag "tuple_elem_not_canonicalized")))))
(e-runtime-error (tag "expr_not_canonicalized")))
(d-let
(p-assign (ident "y"))
(e-runtime-error (tag "expr_not_canonicalized"))))
@ -360,9 +341,9 @@ y =
~~~clojure
(inferred-types
(defs
(patt (type "(Num(Int(_size)), Num(Int(_size2)), Num(Int(_size3)), Error, Error, Error, Error, Num(Int(_size4)), Error, Num(Int(_size5)), Num(Int(_size6)), Error, Error, Error)"))
(patt (type "Error"))
(patt (type "Error")))
(expressions
(expr (type "(Num(Int(_size)), Num(Int(_size2)), Num(Int(_size3)), Error, Error, Error, Error, Num(Int(_size4)), Error, Num(Int(_size5)), Num(Int(_size6)), Error, Error, Error)"))
(expr (type "Error"))
(expr (type "Error"))))
~~~