Add if-without-else

This commit is contained in:
Richard Feldman 2025-11-20 16:49:48 -05:00
parent d4de4ba4d5
commit 4e2ee4e38c
No known key found for this signature in database
16 changed files with 325 additions and 318 deletions

View file

@ -35,6 +35,10 @@ pub const AutoImportedType = struct {
env: *ModuleEnv,
parse_ir: *AST,
/// Track whether we're in statement position (true) or expression position (false)
/// Statement position: if without else is OK (default)
/// Expression position: if without else is ERROR (explicitly set in assignments, etc.)
in_statement_position: bool = true,
scopes: std.ArrayList(Scope) = .{},
/// Special scope for rigid type variables in annotations
type_vars_scope: base.Scratch(TypeVarScope),
@ -712,8 +716,11 @@ fn canonicalizeAssociatedDecl(
}
};
// Canonicalize the body expression
// Canonicalize the body expression in expression context (RHS of assignment)
const saved_stmt_pos = self.in_statement_position;
self.in_statement_position = false;
const can_expr = try self.canonicalizeExprOrMalformed(decl.body);
self.in_statement_position = saved_stmt_pos;
// Create the def with no annotation (type annotations are handled via canonicalizeAssociatedDeclWithAnno)
const def = CIR.Def{
@ -749,8 +756,11 @@ fn canonicalizeAssociatedDeclWithAnno(
}
};
// Canonicalize the body expression
// Canonicalize the body expression in expression context (RHS of assignment)
const saved_stmt_pos = self.in_statement_position;
self.in_statement_position = false;
const can_expr = try self.canonicalizeExprOrMalformed(decl.body);
self.in_statement_position = saved_stmt_pos;
// Create the annotation structure
const annotation = CIR.Annotation{
@ -4279,6 +4289,68 @@ pub fn canonicalizeExpr(
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
},
.if_without_else => |e| {
// Statement form: if without else
const region = self.parse_ir.tokenizedRegionToRegion(e.region);
// Check if we're in expression context (e.g., assignment, function call)
// If so, emit error explaining that if-expressions need else
if (!self.in_statement_position) {
const malformed_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{
.if_expr_without_else = .{ .region = region },
});
return CanonicalizedExpr{ .idx = malformed_idx, .free_vars = null };
}
// Desugar to if-then-else with empty record {} as the final else
// Type checking will ensure the then-branch also has type {}
const free_vars_start = self.scratch_free_vars.top();
// Canonicalize condition
const can_cond = try self.canonicalizeExpr(e.condition) orelse {
const ast_cond = self.parse_ir.store.getExpr(e.condition);
const cond_region = self.parse_ir.tokenizedRegionToRegion(ast_cond.to_tokenized_region());
const malformed_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{
.if_condition_not_canonicalized = .{ .region = cond_region },
});
return CanonicalizedExpr{ .idx = malformed_idx, .free_vars = null };
};
// Canonicalize then branch
const can_then = try self.canonicalizeExpr(e.then) orelse {
const ast_then = self.parse_ir.store.getExpr(e.then);
const then_region = self.parse_ir.tokenizedRegionToRegion(ast_then.to_tokenized_region());
const malformed_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{
.if_then_not_canonicalized = .{ .region = then_region },
});
return CanonicalizedExpr{ .idx = malformed_idx, .free_vars = null };
};
// Create an empty record {} as the implicit else
const empty_record_idx = try self.env.addExpr(CIR.Expr{ .e_empty_record = .{} }, region);
// Create single if branch
const scratch_top = self.env.store.scratchIfBranchTop();
const if_branch = Expr.IfBranch{
.cond = can_cond.idx,
.body = can_then.idx,
};
const if_branch_idx = try self.env.addIfBranch(if_branch, region);
try self.env.store.addScratchIfBranch(if_branch_idx);
const branches_span = try self.env.store.ifBranchSpanFrom(scratch_top);
// Create if expression with empty record as final else
const expr_idx = try self.env.addExpr(CIR.Expr{
.e_if = .{
.branches = branches_span,
.final_else = empty_record_idx,
},
}, region);
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
},
.match => |m| {
const region = self.parse_ir.tokenizedRegionToRegion(m.region);

View file

@ -76,6 +76,9 @@ pub const Diagnostic = union(enum) {
if_else_not_canonicalized: struct {
region: Region,
},
if_expr_without_else: struct {
region: Region,
},
malformed_type_annotation: struct {
region: Region,
},

View file

@ -764,6 +764,16 @@ pub fn diagnosticToReport(self: *Self, diagnostic: CIR.Diagnostic, allocator: st
try report.document.addReflowingText(").");
break :blk report;
},
.if_then_not_canonicalized => |_| blk: {
var report = Report.init(allocator, "INVALID IF BRANCH", .runtime_error);
try report.document.addReflowingText("The branch in this ");
try report.document.addKeyword("if");
try report.document.addReflowingText(" expression could not be processed.");
try report.document.addLineBreak();
try report.document.addLineBreak();
try report.document.addReflowingText("The branch must contain a valid expression. Check for syntax errors or missing values.");
break :blk report;
},
.if_else_not_canonicalized => |_| blk: {
var report = Report.init(allocator, "INVALID IF BRANCH", .runtime_error);
try report.document.addReflowingText("The ");
@ -777,12 +787,33 @@ pub fn diagnosticToReport(self: *Self, diagnostic: CIR.Diagnostic, allocator: st
try report.document.addKeyword("else");
try report.document.addReflowingText(" branch must contain a valid expression. Check for syntax errors or missing values.");
try report.document.addLineBreak();
try report.document.addLineBreak();
try report.document.addReflowingText("Note: Every ");
break :blk report;
},
.if_expr_without_else => |_| blk: {
var report = Report.init(allocator, "IF EXPRESSION WITHOUT ELSE", .runtime_error);
try report.document.addReflowingText("This ");
try report.document.addKeyword("if");
try report.document.addReflowingText(" expression in Roc must have an ");
try report.document.addReflowingText(" has no ");
try report.document.addKeyword("else");
try report.document.addReflowingText(" branch, and both branches must have the same type.");
try report.document.addReflowingText(" branch, but it's being used as an expression (assigned to a variable, passed to a function, etc.).");
try report.document.addLineBreak();
try report.document.addLineBreak();
try report.document.addReflowingText("You can only use ");
try report.document.addKeyword("if");
try report.document.addReflowingText(" without ");
try report.document.addKeyword("else");
try report.document.addReflowingText(" when it's a statement. When ");
try report.document.addKeyword("if");
try report.document.addReflowingText(" is used as an expression that evaluates to a value, ");
try report.document.addKeyword("else");
try report.document.addReflowingText(" is required because otherwise there wouldn't always be a value available.");
try report.document.addLineBreak();
try report.document.addLineBreak();
try report.document.addReflowingText("Either add an ");
try report.document.addKeyword("else");
try report.document.addReflowingText(" branch, or use this ");
try report.document.addKeyword("if");
try report.document.addReflowingText(" as a standalone statement.");
break :blk report;
},
.pattern_not_canonicalized => |_| blk: {

View file

@ -208,4 +208,5 @@ pub const Tag = enum {
diag_underscore_in_type_declaration,
diagnostic_exposed_but_not_implemented,
diag_redundant_exposed,
diag_if_expr_without_else,
};

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 = 58;
pub const MODULEENV_DIAGNOSTIC_NODE_COUNT = 59;
/// Count of the expression nodes in the ModuleEnv
pub const MODULEENV_EXPR_NODE_COUNT = 35;
/// Count of the statement nodes in the ModuleEnv
@ -2746,6 +2746,10 @@ pub fn addDiagnostic(store: *NodeStore, reason: CIR.Diagnostic) Allocator.Error!
node.tag = .diag_if_else_not_canonicalized;
region = r.region;
},
.if_expr_without_else => |r| {
node.tag = .diag_if_expr_without_else;
region = r.region;
},
.malformed_type_annotation => |r| {
node.tag = .diag_malformed_type_annotation;
region = r.region;
@ -3088,6 +3092,9 @@ pub fn getDiagnostic(store: *const NodeStore, diagnostic: CIR.Diagnostic.Idx) CI
.diag_if_else_not_canonicalized => return CIR.Diagnostic{ .if_else_not_canonicalized = .{
.region = store.getRegionAt(node_idx),
} },
.diag_if_expr_without_else => return CIR.Diagnostic{ .if_expr_without_else = .{
.region = store.getRegionAt(node_idx),
} },
.diag_var_across_function_boundary => return CIR.Diagnostic{ .var_across_function_boundary = .{
.region = store.getRegionAt(node_idx),
} },

View file

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

View file

@ -1141,6 +1141,27 @@ const Formatter = struct {
}
_ = try fmt.formatExpr(i.@"else");
},
.if_without_else => |i| {
try fmt.pushAll("if");
const cond_region = fmt.nodeRegion(@intFromEnum(i.condition));
var flushed = try fmt.flushCommentsBefore(cond_region.start);
if (flushed) {
fmt.curr_indent += 1;
try fmt.pushIndent();
} else {
try fmt.push(' ');
}
_ = try fmt.formatExpr(i.condition);
const then_region = fmt.nodeRegion(@intFromEnum(i.then));
flushed = try fmt.flushCommentsBefore(then_region.start);
if (flushed) {
fmt.curr_indent += 1;
try fmt.pushIndent();
} else {
try fmt.push(' ');
}
_ = try fmt.formatExpr(i.then);
},
.match => |m| {
try fmt.pushAll("match ");
_ = try fmt.formatExpr(m.expr);
@ -2186,6 +2207,13 @@ const Formatter = struct {
return fmt.nodeWillBeMultiline(AST.Expr.Idx, i.@"else");
},
.if_without_else => |i| {
if (fmt.nodeWillBeMultiline(AST.Expr.Idx, i.condition)) {
return true;
}
return fmt.nodeWillBeMultiline(AST.Expr.Idx, i.then);
},
.local_dispatch => |l| {
if (fmt.nodeWillBeMultiline(AST.Expr.Idx, l.left)) {
return true;

View file

@ -2336,6 +2336,11 @@ pub const Expr = union(enum) {
@"else": Expr.Idx,
region: TokenizedRegion,
},
if_without_else: struct {
condition: Expr.Idx,
then: Expr.Idx,
region: TokenizedRegion,
},
match: struct {
expr: Expr.Idx,
branches: MatchBranch.Span,
@ -2401,6 +2406,7 @@ pub const Expr = union(enum) {
.suffix_single_question => |e| e.region,
.apply => |e| e.region,
.if_then_else => |e| e.region,
.if_without_else => |e| e.region,
.match => |e| e.region,
.dbg => |e| e.region,
.block => |e| e.region,
@ -2603,6 +2609,17 @@ pub const Expr = union(enum) {
try tree.endNode(begin, attrs);
},
.if_without_else => |stmt| {
const begin = tree.beginNode();
try tree.pushStaticAtom("e-if-without-else");
try ast.appendRegionInfoToSexprTree(env, tree, stmt.region);
const attrs = tree.beginNode();
try ast.store.getExpr(stmt.condition).pushToSExprTree(gpa, env, ast, tree);
try ast.store.getExpr(stmt.then).pushToSExprTree(gpa, env, ast, tree);
try tree.endNode(begin, attrs);
},
.match => |a| {
const begin = tree.beginNode();
try tree.pushStaticAtom("e-match");

View file

@ -402,6 +402,11 @@ pub const Tag = enum {
/// * lhs - node index of expr
/// * rhs - RHS DESCRIPTION
if_then_else,
/// If-statement (no else branch) - statement form of if, returns unit type
/// Example: if Bool.true {}
/// * lhs - node index of condition expr
/// * rhs - node index of then branch expr
if_without_else,
/// DESCRIPTION
/// Example: EXAMPLE
/// * lhs - start index of extra_data

View file

@ -687,6 +687,12 @@ pub fn addExpr(store: *NodeStore, expr: AST.Expr) std.mem.Allocator.Error!AST.Ex
try store.extra_data.append(store.gpa, @intFromEnum(i.then));
try store.extra_data.append(store.gpa, @intFromEnum(i.@"else"));
},
.if_without_else => |i| {
node.tag = .if_without_else;
node.region = i.region;
node.data.lhs = @intFromEnum(i.condition);
node.data.rhs = @intFromEnum(i.then);
},
.match => |m| {
node.tag = .match;
node.region = m.region;
@ -1580,6 +1586,13 @@ pub fn getExpr(store: *const NodeStore, expr_idx: AST.Expr.Idx) AST.Expr {
.@"else" = @enumFromInt(else_ed),
} };
},
.if_without_else => {
return .{ .if_without_else = .{
.region = node.region,
.condition = @enumFromInt(node.data.lhs),
.then = @enumFromInt(node.data.rhs),
} };
},
.match => {
return .{ .match = .{
.region = node.region,

View file

@ -2089,17 +2089,22 @@ pub fn parseExprWithBp(self: *Parser, min_bp: u8) Error!AST.Expr.Idx {
const condition = try self.parseExpr();
const then = try self.parseExpr();
if (self.peek() != .KwElse) {
// Point to the if keyword for missing else error
return try self.pushMalformed(AST.Expr.Idx, .no_else, start);
// Statement form: if without else
expr = try self.store.addExpr(.{ .if_without_else = .{
.region = .{ .start = start, .end = self.pos },
.condition = condition,
.then = then,
} });
} else {
self.advance();
const else_idx = try self.parseExpr();
expr = try self.store.addExpr(.{ .if_then_else = .{
.region = .{ .start = start, .end = self.pos },
.condition = condition,
.then = then,
.@"else" = else_idx,
} });
}
self.advance();
const else_idx = try self.parseExpr();
expr = try self.store.addExpr(.{ .if_then_else = .{
.region = .{ .start = start, .end = self.pos },
.condition = condition,
.then = then,
.@"else" = else_idx,
} });
},
.KwMatch => {
self.advance();

View file

@ -8,31 +8,38 @@ type=snippet
foo = if tru 0
~~~
# EXPECTED
IF WITHOUT ELSE - expr_if_missing_else.md:1:7:1:9
UNRECOGNIZED SYNTAX - expr_if_missing_else.md:1:7:1:15
UNDEFINED VARIABLE - expr_if_missing_else.md:1:10:1:13
INCOMPATIBLE IF BRANCHES - expr_if_missing_else.md:1:7:1:7
# PROBLEMS
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
**UNDEFINED VARIABLE**
Nothing is named `tru` in this scope.
Is there an `import` or `exposing` missing up-top?
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`.
**expr_if_missing_else.md:1:7:1:9:**
**expr_if_missing_else.md:1:10:1:13:**
```roc
foo = if tru 0
```
^^
^^^
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
**expr_if_missing_else.md:1:7:1:15:**
**INCOMPATIBLE IF BRANCHES**
This `if` has an `else` branch with a different type from it's `then` branch:
**expr_if_missing_else.md:1:7:**
```roc
foo = if tru 0
```
^^^^^^^^
This might be a syntax error, an unsupported language feature, or a typo.
The `else` branch has the type:
_{}_
But the `then` branch has the type:
_Num(_size)_
All branches in an `if` must have compatible types.
Note: You can wrap branches in a tag to make them compatible.
To learn about tags, see <https://www.roc-lang.org/tutorial#tags>
# TOKENS
~~~zig
@ -46,18 +53,26 @@ EndOfFile,
(statements
(s-decl
(p-ident (raw "foo"))
(e-malformed (reason "no_else")))))
(e-if-without-else
(e-ident (raw "tru"))
(e-int (raw "0"))))))
~~~
# FORMATTED
~~~roc
foo =
NO CHANGE
~~~
# CANONICALIZE
~~~clojure
(can-ir
(d-let
(p-assign (ident "foo"))
(e-runtime-error (tag "expr_not_canonicalized"))))
(e-if
(if-branches
(if-branch
(e-runtime-error (tag "ident_not_in_scope"))
(e-num (value "0"))))
(if-else
(e-empty_record)))))
~~~
# TYPES
~~~clojure

View file

@ -14,11 +14,7 @@ match value {
# EXPECTED
PARSE ERROR - guards_1.md:2:7:2:7
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:2:16:2:18
IF WITHOUT ELSE - guards_1.md:2:7:2:9
UNEXPECTED TOKEN IN PATTERN - guards_1.md:2:20:2:30
PARSE ERROR - guards_1.md:2:30:2:30
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:2:30:2:32
UNEXPECTED TOKEN IN PATTERN - guards_1.md:2:32:2:35
PARSE ERROR - guards_1.md:2:19:2:20
PARSE ERROR - guards_1.md:2:43:2:43
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:2:43:2:44
UNEXPECTED TOKEN IN PATTERN - guards_1.md:2:44:2:44
@ -26,19 +22,15 @@ PARSE ERROR - guards_1.md:2:44:2:44
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:2:44:2:45
PARSE ERROR - guards_1.md:3:7:3:7
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:3:16:3:18
IF WITHOUT ELSE - guards_1.md:3:7:3:9
UNEXPECTED TOKEN IN PATTERN - guards_1.md:3:20:3:30
PARSE ERROR - guards_1.md:3:30:3:30
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:3:30:3:32
UNEXPECTED TOKEN IN PATTERN - guards_1.md:3:32:3:35
PARSE ERROR - guards_1.md:3:19:3:20
PARSE ERROR - guards_1.md:3:43:3:43
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:3:43:3:44
UNEXPECTED TOKEN IN PATTERN - guards_1.md:3:44:3:44
PARSE ERROR - guards_1.md:3:44:3:44
UNEXPECTED TOKEN IN EXPRESSION - guards_1.md:3:44:3:45
UNDEFINED VARIABLE - guards_1.md:1:7:1:12
UNRECOGNIZED SYNTAX - guards_1.md:2:7:2:20
UNUSED VARIABLE - guards_1.md:2:5:2:6
INVALID IF BRANCH - :0:0:0:0
UNRECOGNIZED SYNTAX - guards_1.md:2:43:2:44
# PROBLEMS
**PARSE ERROR**
A parsing error occurred: `match_branch_missing_arrow`
@ -62,60 +54,15 @@ Expressions can be identifiers, literals, function calls, or operators.
^^
**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 evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
**guards_1.md:2:7:2:9:**
```roc
x if x > 0 => "positive: ${Num.toStr x}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **positive: ** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_1.md:2:20:2:30:**
```roc
x if x > 0 => "positive: ${Num.toStr x}"
```
^^^^^^^^^^
**PARSE ERROR**
A parsing error occurred: `match_branch_missing_arrow`
A parsing error occurred: `string_expected_close_interpolation`
This is an unexpected parsing error. Please check your syntax.
**guards_1.md:2:30:2:30:**
**guards_1.md:2:19:2:20:**
```roc
x if x > 0 => "positive: ${Num.toStr x}"
```
^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **${** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**guards_1.md:2:30:2:32:**
```roc
x if x > 0 => "positive: ${Num.toStr x}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **Num** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_1.md:2:32:2:35:**
```roc
x if x > 0 => "positive: ${Num.toStr x}"
```
^^^
^
**PARSE ERROR**
@ -195,60 +142,15 @@ Expressions can be identifiers, literals, function calls, or operators.
^^
**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 evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
**guards_1.md:3:7:3:9:**
```roc
x if x < 0 => "negative: ${Num.toStr x}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **negative: ** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_1.md:3:20:3:30:**
```roc
x if x < 0 => "negative: ${Num.toStr x}"
```
^^^^^^^^^^
**PARSE ERROR**
A parsing error occurred: `match_branch_missing_arrow`
A parsing error occurred: `string_expected_close_interpolation`
This is an unexpected parsing error. Please check your syntax.
**guards_1.md:3:30:3:30:**
**guards_1.md:3:19:3:20:**
```roc
x if x < 0 => "negative: ${Num.toStr x}"
```
^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **${** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**guards_1.md:3:30:3:32:**
```roc
x if x < 0 => "negative: ${Num.toStr x}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **Num** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_1.md:3:32:3:35:**
```roc
x if x < 0 => "negative: ${Num.toStr x}"
```
^^^
^
**PARSE ERROR**
@ -317,29 +219,22 @@ match value {
^^^^^
**INVALID IF BRANCH**
The branch in this `if` expression could not be processed.
The branch must contain a valid expression. Check for syntax errors or missing values.
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
**guards_1.md:2:7:2:20:**
**guards_1.md:2:43:2:44:**
```roc
x if x > 0 => "positive: ${Num.toStr x}"
```
^^^^^^^^^^^^^
^
This might be a syntax error, an unsupported language feature, or a typo.
**UNUSED VARIABLE**
Variable `x` is not used anywhere in your code.
If you don't need this variable, prefix it with an underscore like `_x` to suppress this warning.
The unused variable is declared here:
**guards_1.md:2:5:2:6:**
```roc
x if x > 0 => "positive: ${Num.toStr x}"
```
^
# TOKENS
~~~zig
KwMatch,LowerIdent,OpenCurly,
@ -356,24 +251,26 @@ EndOfFile,
(branches
(branch
(p-ident (raw "x"))
(e-malformed (reason "no_else")))
(e-if-without-else
(e-binop (op ">")
(e-ident (raw "x"))
(e-int (raw "0")))
(e-malformed (reason "expr_unexpected_token"))))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(p-string (raw """))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-ident (raw "x"))
(e-malformed (reason "no_else")))
(e-if-without-else
(e-binop (op "<")
(e-ident (raw "x"))
(e-int (raw "0")))
(e-malformed (reason "expr_unexpected_token"))))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(p-string (raw """))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
@ -386,12 +283,10 @@ EndOfFile,
# FORMATTED
~~~roc
match value {
x =>
x => if x > 0
=>
=>
=>
x =>
=>
x => if x < 0
=>
=>
_ => "other"

View file

@ -14,11 +14,7 @@ match value {
# EXPECTED
PARSE ERROR - guards_2.md:2:25:2:25
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:2:47:2:49
IF WITHOUT ELSE - guards_2.md:2:25:2:27
UNEXPECTED TOKEN IN PATTERN - guards_2.md:2:51:2:75
PARSE ERROR - guards_2.md:2:75:2:75
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:2:75:2:77
UNEXPECTED TOKEN IN PATTERN - guards_2.md:2:77:2:80
PARSE ERROR - guards_2.md:2:50:2:51
PARSE ERROR - guards_2.md:2:92:2:92
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:2:92:2:93
UNEXPECTED TOKEN IN PATTERN - guards_2.md:2:93:2:93
@ -26,20 +22,16 @@ PARSE ERROR - guards_2.md:2:93:2:93
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:2:93:2:94
PARSE ERROR - guards_2.md:3:12:3:12
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:3:22:3:24
IF WITHOUT ELSE - guards_2.md:3:12:3:14
UNEXPECTED TOKEN IN PATTERN - guards_2.md:3:26:3:48
PARSE ERROR - guards_2.md:3:48:3:48
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:3:48:3:50
UNEXPECTED TOKEN IN PATTERN - guards_2.md:3:50:3:53
PARSE ERROR - guards_2.md:3:25:3:26
PARSE ERROR - guards_2.md:3:61:3:61
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:3:61:3:62
UNEXPECTED TOKEN IN PATTERN - guards_2.md:3:62:3:62
PARSE ERROR - guards_2.md:3:62:3:62
UNEXPECTED TOKEN IN EXPRESSION - guards_2.md:3:62:3:63
UNDEFINED VARIABLE - guards_2.md:1:7:1:12
UNRECOGNIZED SYNTAX - guards_2.md:2:25:2:51
INVALID IF BRANCH - :0:0:0:0
UNUSED VARIABLE - guards_2.md:2:6:2:11
UNUSED VARIABLE - guards_2.md:1:1:1:1
UNRECOGNIZED SYNTAX - guards_2.md:2:92:2:93
# PROBLEMS
**PARSE ERROR**
A parsing error occurred: `match_branch_missing_arrow`
@ -63,60 +55,15 @@ Expressions can be identifiers, literals, function calls, or operators.
^^
**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 evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
**guards_2.md:2:25:2:27:**
```roc
[first, .. as rest] if List.len(rest) > 5 => "long list starting with ${Num.toStr first}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **long list starting with ** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_2.md:2:51:2:75:**
```roc
[first, .. as rest] if List.len(rest) > 5 => "long list starting with ${Num.toStr first}"
```
^^^^^^^^^^^^^^^^^^^^^^^^
**PARSE ERROR**
A parsing error occurred: `match_branch_missing_arrow`
A parsing error occurred: `string_expected_close_interpolation`
This is an unexpected parsing error. Please check your syntax.
**guards_2.md:2:75:2:75:**
**guards_2.md:2:50:2:51:**
```roc
[first, .. as rest] if List.len(rest) > 5 => "long list starting with ${Num.toStr first}"
```
^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **${** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**guards_2.md:2:75:2:77:**
```roc
[first, .. as rest] if List.len(rest) > 5 => "long list starting with ${Num.toStr first}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **Num** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_2.md:2:77:2:80:**
```roc
[first, .. as rest] if List.len(rest) > 5 => "long list starting with ${Num.toStr first}"
```
^^^
^
**PARSE ERROR**
@ -196,60 +143,15 @@ Expressions can be identifiers, literals, function calls, or operators.
^^
**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 evaluate to a value), it must have an `else` branch to specify what value to use when the condition is `False`.
**guards_2.md:3:12:3:14:**
```roc
[x, y] if x == y => "pair of equal values: ${Num.toStr x}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **pair of equal values: ** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_2.md:3:26:3:48:**
```roc
[x, y] if x == y => "pair of equal values: ${Num.toStr x}"
```
^^^^^^^^^^^^^^^^^^^^^^
**PARSE ERROR**
A parsing error occurred: `match_branch_missing_arrow`
A parsing error occurred: `string_expected_close_interpolation`
This is an unexpected parsing error. Please check your syntax.
**guards_2.md:3:48:3:48:**
**guards_2.md:3:25:3:26:**
```roc
[x, y] if x == y => "pair of equal values: ${Num.toStr x}"
```
^
**UNEXPECTED TOKEN IN EXPRESSION**
The token **${** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
**guards_2.md:3:48:3:50:**
```roc
[x, y] if x == y => "pair of equal values: ${Num.toStr x}"
```
^^
**UNEXPECTED TOKEN IN PATTERN**
The token **Num** is not expected in a pattern.
Patterns can contain identifiers, literals, lists, records, or tags.
**guards_2.md:3:50:3:53:**
```roc
[x, y] if x == y => "pair of equal values: ${Num.toStr x}"
```
^^^
^
**PARSE ERROR**
@ -318,16 +220,10 @@ match value {
^^^^^
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
**INVALID IF BRANCH**
The branch in this `if` expression could not be processed.
**guards_2.md:2:25:2:51:**
```roc
[first, .. as rest] if List.len(rest) > 5 => "long list starting with ${Num.toStr first}"
```
^^^^^^^^^^^^^^^^^^^^^^^^^^
This might be a syntax error, an unsupported language feature, or a typo.
The branch must contain a valid expression. Check for syntax errors or missing values.
**UNUSED VARIABLE**
Variable `first` is not used anywhere in your code.
@ -341,17 +237,16 @@ The unused variable is declared here:
^^^^^
**UNUSED VARIABLE**
Variable `rest` is not used anywhere in your code.
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
If you don't need this variable, prefix it with an underscore like `_rest` to suppress this warning.
The unused variable is declared here:
**guards_2.md:1:1:1:1:**
**guards_2.md:2:92:2:93:**
```roc
match value {
[first, .. as rest] if List.len(rest) > 5 => "long list starting with ${Num.toStr first}"
```
^
^
This might be a syntax error, an unsupported language feature, or a typo.
# TOKENS
~~~zig
@ -371,12 +266,15 @@ EndOfFile,
(p-list
(p-ident (raw "first"))
(p-list-rest (name "rest")))
(e-malformed (reason "no_else")))
(e-if-without-else
(e-binop (op ">")
(e-apply
(e-ident (raw "List.len"))
(e-ident (raw "rest")))
(e-int (raw "5")))
(e-malformed (reason "expr_unexpected_token"))))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(p-string (raw """))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
@ -385,12 +283,13 @@ EndOfFile,
(p-list
(p-ident (raw "x"))
(p-ident (raw "y")))
(e-malformed (reason "no_else")))
(e-if-without-else
(e-binop (op "==")
(e-ident (raw "x"))
(e-ident (raw "y")))
(e-malformed (reason "expr_unexpected_token"))))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
(p-string (raw """))
(e-malformed (reason "expr_unexpected_token")))
(branch
(p-malformed (tag "pattern_unexpected_token"))
@ -403,12 +302,10 @@ EndOfFile,
# FORMATTED
~~~roc
match value {
[first, .. as rest] =>
[first, .. as rest] => if List.len(rest) > 5
=>
=>
=>
[x, y] =>
=>
[x, y] => if x == y
=>
=>
_ => "other"

View file

@ -16,7 +16,7 @@ type=expr
~~~
# EXPECTED
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:2:7:2:8
IF WITHOUT ELSE - record_different_fields_reserved_error.md:2:5:2:7
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:2:22:2:23
UNEXPECTED TOKEN IN TYPE ANNOTATION - record_different_fields_reserved_error.md:3:11:3:12
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:3:12:3:25
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:3:25:3:26
@ -32,7 +32,8 @@ UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:6:19:
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:7:5:7:7
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:7:7:7:8
UNEXPECTED TOKEN IN EXPRESSION - record_different_fields_reserved_error.md:7:19:7:20
UNRECOGNIZED SYNTAX - record_different_fields_reserved_error.md:2:5:2:23
INVALID IF CONDITION - :0:0:0:0
UNRECOGNIZED SYNTAX - record_different_fields_reserved_error.md:2:22:2:23
MALFORMED TYPE - record_different_fields_reserved_error.md:3:11:3:12
UNRECOGNIZED SYNTAX - record_different_fields_reserved_error.md:3:12:3:25
UNRECOGNIZED SYNTAX - record_different_fields_reserved_error.md:3:25:3:26
@ -64,16 +65,15 @@ Expressions can be identifiers, literals, function calls, or operators.
^
**IF WITHOUT ELSE**
This `if` is being used as an expression, but it doesn't have an `else`.
**UNEXPECTED TOKEN IN EXPRESSION**
The token **,** is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
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`.
**record_different_fields_reserved_error.md:2:5:2:7:**
**record_different_fields_reserved_error.md:2:22:2:23:**
```roc
if: "conditional",
```
^^
^
**UNEXPECTED TOKEN IN TYPE ANNOTATION**
@ -241,14 +241,19 @@ Expressions can be identifiers, literals, function calls, or operators.
^
**INVALID IF CONDITION**
The condition in this `if` expression could not be processed.
The condition must be a valid expression that evaluates to a `Bool` value (`Bool.true` or `Bool.false`).
**UNRECOGNIZED SYNTAX**
I don't recognize this syntax.
**record_different_fields_reserved_error.md:2:5:2:23:**
**record_different_fields_reserved_error.md:2:22:2:23:**
```roc
if: "conditional",
```
^^^^^^^^^^^^^^^^^^
^
This might be a syntax error, an unsupported language feature, or a typo.
@ -481,7 +486,11 @@ EndOfFile,
~~~clojure
(e-block
(statements
(e-malformed (reason "no_else"))
(e-if-without-else
(e-malformed (reason "expr_unexpected_token"))
(e-string
(e-string-part (raw "conditional"))))
(e-malformed (reason "expr_unexpected_token"))
(s-type-anno (name "when")
(ty-malformed (tag "ty_anno_unexpected_token")))
(e-malformed (reason "expr_unexpected_token"))
@ -509,6 +518,7 @@ EndOfFile,
# FORMATTED
~~~roc
{
if "conditional"
when :
@ -526,6 +536,8 @@ EndOfFile,
# CANONICALIZE
~~~clojure
(e-block
(s-expr
(e-runtime-error (tag "if_condition_not_canonicalized")))
(s-expr
(e-runtime-error (tag "expr_not_canonicalized")))
(s-let