Tweak an error message

This commit is contained in:
Richard Feldman 2025-08-16 12:49:18 -04:00
parent 843da2f239
commit b490acffee
No known key found for this signature in database
7 changed files with 157 additions and 45 deletions

View file

@ -143,17 +143,18 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
var report = reporting.Report.init(allocator, title, .runtime_error);
try report.document.addText(body);
// Add the region information from the diagnostic if valid
if (diagnostic.region.start.offset < diagnostic.region.end.offset and
diagnostic.region.end.offset <= self.env.source.len) {
if (diagnostic.region.start.offset < diagnostic.region.end.offset and
diagnostic.region.end.offset <= self.env.source.len)
{
// Calculate line starts if not already done
var env = self.env.*;
if (env.line_starts.items.items.len == 0) {
try env.calcLineStarts(allocator);
}
// Convert region to RegionInfo
const region_info = base.RegionInfo.position(
self.env.source,
@ -164,7 +165,7 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
// If we can't calculate region info, just return the report without source context
return report;
};
// Add source region to the report
try report.document.addSourceRegion(
region_info,
@ -174,7 +175,7 @@ pub fn tokenizeDiagnosticToReport(self: *AST, diagnostic: tokenize.Diagnostic, a
env.line_starts.items.items,
);
}
return report;
}
@ -541,9 +542,11 @@ pub fn parseDiagnosticToReport(self: *AST, env: *const CommonEnv, diagnostic: Di
try report.document.addLineBreak();
try report.document.addReflowingText("When ");
try report.document.addKeyword("if");
try report.document.addReflowingText(" is used as an expression (to produce a value), it must have an ");
try report.document.addReflowingText(" is used as an expression (to evaluate to a value), it must have an ");
try report.document.addKeyword("else");
try report.document.addReflowingText(" branch to handle the case when the condition is false.");
try report.document.addReflowingText(" branch to specify what value to use when the condition is ");
try report.document.addKeyword("False");
try report.document.addReflowingText(".");
},
else => {
const tag_name = @tagName(diagnostic.tag);

View file

@ -1168,29 +1168,65 @@ fn parseStmtByType(self: *Parser, statementType: StatementType) Error!?AST.State
// continue to parse final expression
}
},
// Expect to parse a Type Annotation, e.g. `Foo a : (a,a)`
// Could be a Type Annotation (e.g. `Foo a : (a,a)`) or a pattern assignment (e.g. `Pair(x, y) = expr`)
.UpperIdent => {
const start = self.pos;
if (statementType == .top_level) {
const header = try self.parseTypeHeader();
if (self.peek() != .OpColon and self.peek() != .OpColonEqual) {
// Point to the unexpected token (e.g., "U8" in "List U8")
return try self.pushMalformed(AST.Statement.Idx, .expected_colon_after_type_annotation, self.pos);
// Look ahead to determine if this is a type declaration or pattern assignment
// We need to check what comes after the identifier and any parentheses
var lookahead_pos = self.pos + 1;
var paren_depth: u32 = 0;
// Skip past any parentheses to find what operator follows
while (lookahead_pos < self.tok_buf.tokens.len) {
const tok = self.tok_buf.tokens.items(.tag)[lookahead_pos];
if (tok == .OpenRound or tok == .NoSpaceOpenRound) {
paren_depth += 1;
} else if (tok == .CloseRound) {
if (paren_depth == 0) break;
paren_depth -= 1;
if (paren_depth == 0) {
lookahead_pos += 1;
break;
}
} else if (paren_depth == 0) {
// We're not in parentheses, so check what token we have
break;
}
lookahead_pos += 1;
}
// Check what token follows the identifier (and any parentheses)
const next_tok = if (lookahead_pos < self.tok_buf.tokens.len)
self.tok_buf.tokens.items(.tag)[lookahead_pos]
else
.EndOfFile;
// If it's OpAssign, this is a pattern assignment, not a type declaration
if (next_tok == .OpAssign) {
// Fall through to parse as a pattern assignment
} else {
// Parse as a type declaration
const header = try self.parseTypeHeader();
if (self.peek() != .OpColon and self.peek() != .OpColonEqual) {
// Point to the unexpected token (e.g., "U8" in "List U8")
return try self.pushMalformed(AST.Statement.Idx, .expected_colon_after_type_annotation, self.pos);
}
const kind: AST.TypeDeclKind = if (self.peek() == .OpColonEqual) .nominal else .alias;
self.advance();
const anno = try self.parseTypeAnno(.not_looking_for_args);
const where_clause = try self.parseWhereConstraint();
// Use the type annotation's end position if there's no where clause,
// otherwise use the current position (after parsing where clause)
const statement_idx = try self.store.addStatement(.{ .type_decl = .{
.header = header,
.anno = anno,
.where = where_clause,
.kind = kind,
.region = .{ .start = start, .end = self.pos },
} });
return statement_idx;
}
const kind: AST.TypeDeclKind = if (self.peek() == .OpColonEqual) .nominal else .alias;
self.advance();
const anno = try self.parseTypeAnno(.not_looking_for_args);
const where_clause = try self.parseWhereConstraint();
// Use the type annotation's end position if there's no where clause,
// otherwise use the current position (after parsing where clause)
const statement_idx = try self.store.addStatement(.{ .type_decl = .{
.header = header,
.anno = anno,
.where = where_clause,
.kind = kind,
.region = .{ .start = start, .end = self.pos },
} });
return statement_idx;
}
},
.OpenCurly, .OpenRound => {

View file

@ -16,7 +16,7 @@ UNKNOWN OPERATOR - expr_if_missing_else.md:3:7:3:15
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
When `if` is used as an expression (to produce a value), it must have an `else` branch to handle the case when the condition is false.
When `if` is used as an expression (to evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
Here is the problematic code:
**expr_if_missing_else.md:3:7:3:9:**

View file

@ -14,6 +14,12 @@ Pair2(_, y) = Pair(0, 1)
Pair3(_, _) = Pair(0, 1)
~~~
# EXPECTED
PARSE ERROR - underscore_type_decl.md:5:1:5:6
PARSE ERROR - underscore_type_decl.md:5:6:5:7
PARSE ERROR - underscore_type_decl.md:5:7:5:8
PARSE ERROR - underscore_type_decl.md:5:8:5:9
PARSE ERROR - underscore_type_decl.md:5:10:5:11
PARSE ERROR - underscore_type_decl.md:5:11:5:12
PARSE ERROR - underscore_type_decl.md:5:13:5:14
PARSE ERROR - underscore_type_decl.md:5:20:5:21
PARSE ERROR - underscore_type_decl.md:5:23:5:24
@ -39,20 +45,80 @@ PARSE ERROR - underscore_type_decl.md:7:25:7:25
MODULE NOT FOUND - underscore_type_decl.md:3:1:3:30
# PROBLEMS
**PARSE ERROR**
Type applications require parentheses around their type arguments.
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
I found a type followed by what looks like a type argument, but they need to be connected with parentheses.
Here is the problematic code:
**underscore_type_decl.md:5:1:5:6:**
```roc
Pair1(x, _) = Pair(0, 1)
```
^^^^^
Instead of:
**List U8**
Use:
**List(U8)**
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
Other valid examples:
`Dict(Str, Num)`
`Result(a, Str)`
`Maybe(List(U64))`
Here is the problematic code:
**underscore_type_decl.md:5:6:5:7:**
```roc
Pair1(x, _) = Pair(0, 1)
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
Here is the problematic code:
**underscore_type_decl.md:5:7:5:8:**
```roc
Pair1(x, _) = Pair(0, 1)
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
Here is the problematic code:
**underscore_type_decl.md:5:8:5:9:**
```roc
Pair1(x, _) = Pair(0, 1)
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
Here is the problematic code:
**underscore_type_decl.md:5:10:5:11:**
```roc
Pair1(x, _) = Pair(0, 1)
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
Here is the problematic code:
**underscore_type_decl.md:5:11:5:12:**
```roc
Pair1(x, _) = Pair(0, 1)
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
Here is the problematic code:
**underscore_type_decl.md:5:13:5:14:**
@ -378,7 +444,13 @@ UpperIdent(7:1-7:6),NoSpaceOpenRound(7:6-7:7),Underscore(7:7-7:8),Comma(7:8-7:9)
(s-import @3.1-3.30 (raw "Module")
(exposing
(exposed-upper-ident @3.25-3.29 (text "Pair"))))
(s-malformed @5.13-5.14 (tag "expected_colon_after_type_annotation"))
(s-malformed @5.1-5.6 (tag "statement_unexpected_token"))
(s-malformed @5.6-5.7 (tag "statement_unexpected_token"))
(s-malformed @5.7-5.8 (tag "statement_unexpected_token"))
(s-malformed @5.8-5.9 (tag "statement_unexpected_token"))
(s-malformed @5.10-5.11 (tag "statement_unexpected_token"))
(s-malformed @5.11-5.12 (tag "statement_unexpected_token"))
(s-malformed @5.13-5.14 (tag "statement_unexpected_token"))
(s-malformed @6.1-6.6 (tag "expected_colon_after_type_annotation"))
(s-malformed @6.6-6.7 (tag "statement_unexpected_token"))
(s-malformed @6.7-6.8 (tag "statement_unexpected_token"))
@ -403,6 +475,7 @@ import Module exposing [Pair]
~~~
# CANONICALIZE
~~~clojure

View file

@ -67,7 +67,7 @@ Here is the problematic code:
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
When `if` is used as an expression (to produce a value), it must have an `else` branch to handle the case when the condition is false.
When `if` is used as an expression (to evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
Here is the problematic code:
**guards_1.md:2:7:2:9:**
@ -212,7 +212,7 @@ Here is the problematic code:
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
When `if` is used as an expression (to produce a value), it must have an `else` branch to handle the case when the condition is false.
When `if` is used as an expression (to evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
Here is the problematic code:
**guards_1.md:3:7:3:9:**

View file

@ -68,7 +68,7 @@ Here is the problematic code:
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
When `if` is used as an expression (to produce a value), it must have an `else` branch to handle the case when the condition is false.
When `if` is used as an expression (to evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
Here is the problematic code:
**guards_2.md:2:25:2:27:**
@ -213,7 +213,7 @@ Here is the problematic code:
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
When `if` is used as an expression (to produce a value), it must have an `else` branch to handle the case when the condition is false.
When `if` is used as an expression (to evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
Here is the problematic code:
**guards_2.md:3:12:3:14:**

View file

@ -52,7 +52,7 @@ Here is the problematic code:
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
When `if` is used as an expression (to produce a value), it must have an `else` branch to handle the case when the condition is false.
When `if` is used as an expression (to evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
Here is the problematic code:
**record_different_fields_reserved_error.md:2:5:2:7:**