Skip to content

Commit

Permalink
Support unwrap choice operator.
Browse files Browse the repository at this point in the history
  • Loading branch information
fubark committed Sep 2, 2024
1 parent a90159d commit 6819984
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 12 deletions.
6 changes: 3 additions & 3 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ The dynamic type defers type checking to runtime. However, it also tracks its ow
* [Choices.](#choices)
* [Initialize choice.](#initialize-choice)
* [Choice `switch`.](#choice-switch)
* [Access choice.](#access-choice)
* [Unwrap choice.](#unwrap-choice)
* [Type aliases.](#type-aliases)
* [Distinct types.](#distinct-types)
* [Traits.](#traits)
Expand Down Expand Up @@ -1441,8 +1441,8 @@ else:
print "Unsupported."
```

### Access choice.
A choice can be accessed by specifying the access operator `.!` before the tagged member name. This will either return the payload or panic at runtime: *Planned Feature*
### Unwrap choice.
A choice can be accessed by specifying the unwrap operator `.!` before the tagged member name. This will either return the payload or panic at runtime:
```cy
var s = Shape{line=20}
print s.!line --> 20
Expand Down
10 changes: 9 additions & 1 deletion src/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pub const NodeType = enum(u7) {
template,
unary_expr,
unwrap,
unwrap_choice,
unwrap_or,
use_alias,
whileCondStmt,
Expand Down Expand Up @@ -295,6 +296,11 @@ const AccessExpr = struct {
right: *Node,
};

const UnwrapChoice = struct {
left: *Node align(8),
right: *Node,
};

const DerefExpr = struct {
left: *Node align(8),
};
Expand Down Expand Up @@ -687,6 +693,7 @@ fn NodeData(comptime node_t: NodeType) type {
.template => TemplateDecl,
.unary_expr => Unary,
.unwrap => Unwrap,
.unwrap_choice => UnwrapChoice,
.unwrap_or => UnwrapOr,
.use_alias => UseAlias,
.whileCondStmt => WhileCondStmt,
Expand Down Expand Up @@ -827,6 +834,7 @@ pub const Node = struct {
.typeAliasDecl => self.cast(.typeAliasDecl).pos,
.unary_expr => self.cast(.unary_expr).child.pos()-1,
.unwrap => self.cast(.unwrap).opt.pos(),
.unwrap_choice => self.cast(.unwrap_choice).left.pos(),
.unwrap_or => self.cast(.unwrap_or).opt.pos(),
.use_alias => self.cast(.use_alias).pos,
.whileInfStmt => self.cast(.whileInfStmt).pos,
Expand Down Expand Up @@ -904,7 +912,7 @@ pub const UnaryOp = enum(u8) {
};

test "ast internals." {
try t.eq(std.enums.values(NodeType).len, 99);
try t.eq(std.enums.values(NodeType).len, 100);
try t.eq(@sizeOf(NodeHeader), 1);
}

Expand Down
14 changes: 12 additions & 2 deletions src/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2620,13 +2620,23 @@ pub const Parser = struct {

// Access expr.
const right = (try self.parseOptName()) orelse {
return self.reportError("Expected ident", &.{});
return self.reportError("Expected ident.", &.{});
};
left = try self.ast.newNodeErase(.accessExpr, .{
.left = left,
.right = right,
});
},
.dot_bang => {
self.advance();
const right = (try self.parseOptName()) orelse {
return self.reportError("Expected ident.", &.{});
};
left = try self.ast.newNodeErase(.unwrap_choice, .{
.left = left,
.right = right,
});
},
.dot_question => {
self.advance();
left = try self.ast.newNodeErase(.unwrap, .{
Expand Down Expand Up @@ -3713,7 +3723,7 @@ fn toBinExprOp(op: cy.tokenizer.TokenType) ?cy.ast.BinaryExprOp {
.as_k, .at, .await_k,
.bang, .bin, .break_k,
.minus_right_angle, .case_k, .catch_k, .coinit_k, .colon, .comma, .context_k, .continue_k, .coresume_k, .coyield_k, .cstruct_k,
.dec, .dot, .dot_question, .dot_dot, .dot_star,
.dec, .dot, .dot_bang, .dot_question, .dot_dot, .dot_star,
.else_k, .enum_k, .err, .error_k, .equal, .equal_right_angle,
.false_k, .float, .for_k, .func_k, .Func_k,
.hex, .ident, .if_k, .mod_k, .indent,
Expand Down
29 changes: 27 additions & 2 deletions src/sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5624,6 +5624,26 @@ pub const ChunkExt = struct {
.accessExpr => {
return try c.semaAccessExpr(expr, false);
},
.unwrap_choice => {
const unwrap = node.cast(.unwrap_choice);
const choice = try c.semaExpr(unwrap.left, .{});
const type_e = c.sema.getType(choice.type.id);
if (type_e.kind != .choice) {
return c.reportError("Expected choice type.", unwrap.left);
}

const enum_t = type_e.sym.cast(.enum_t);
const name = c.ast.nodeString(unwrap.right);
const member = enum_t.getMember(name) orelse {
return c.reportErrorFmt("Choice case `{}` does not exist.", &.{v(name)}, unwrap.right);
};
const loc = try c.ir.pushExpr(.unwrapChoice, c.alloc, member.payloadType, node, .{
.choice = choice.irIdx,
.tag = @intCast(member.val),
.fieldIdx = 1,
});
return ExprResult.initStatic(loc, member.payloadType);
},
.unwrap => {
const opt = try c.semaOptionExpr(node.cast(.unwrap).opt);
const payload_t = if (opt.type.id == bt.Any) bt.Any else b: {
Expand Down Expand Up @@ -6617,8 +6637,13 @@ pub const ChunkExt = struct {

if (left.type.id == right.type.id) {
const left_te = c.sema.types.items[left.type.id];
if (left_te.kind == .option or left_te.kind == .struct_t) {
return semaStructCompare(c, left, leftId, op, right, rightId, left.type.id, node);
switch (left_te.kind) {
.option,
.choice,
.struct_t => {
return semaStructCompare(c, left, leftId, op, right, rightId, left.type.id, node);
},
else => {},
}
}

Expand Down
12 changes: 9 additions & 3 deletions src/sym.zig
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,15 @@ pub const Sym = extern struct {

pub fn getFields(self: *Sym) ?[]const FieldInfo {
switch (self.type) {
.enum_t => return &[_]FieldInfo{
.{ .sym = undefined, .type = bt.Integer, .offset = 0 },
.{ .sym = undefined, .type = bt.Any, .offset = 0 },
.enum_t => {
const enum_t = self.cast(.enum_t);
if (enum_t.isChoiceType) {
return &[_]FieldInfo{
.{ .sym = undefined, .type = bt.Integer, .offset = 0 },
.{ .sym = undefined, .type = bt.Any, .offset = 0 }
};
}
return null;
},
.struct_t => return self.cast(.struct_t).getFields(),
.object_t => return self.cast(.object_t).getFields(),
Expand Down
7 changes: 6 additions & 1 deletion src/tokenizer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ pub const TokenType = enum(u8) {
cstruct_k,
dec,
dot,
dot_question,
dot_bang,
dot_dot,
dot_star,
dot_question,
double_left_angle,
double_right_angle,
double_vert_bar,
Expand Down Expand Up @@ -319,6 +320,10 @@ pub const Tokenizer = struct {
',' => try t.pushToken(.comma, start),
'.' => {
switch (peek(t)) {
'!' => {
advance(t);
try t.pushToken(.dot_bang, start);
},
'.' => {
advance(t);
try t.pushToken(.dot_dot, start);
Expand Down
1 change: 1 addition & 0 deletions test/behavior_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ if (!cy.isWasm) {
run.case("types/choice_access_error.cy");
run.case("types/choice_access_panic.cy");
run.case("types/choice_type.cy");
run.case("types/choice_unwrap_panic.cy");
run.case("types/cstructs.cy");
run.case("types/distinct.cy");
}
Expand Down
5 changes: 5 additions & 0 deletions test/types/choice_type.cy
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,9 @@ res = switch s:
else => 234
test.eq(res, 234)

-- Unwrapping payload.
var rect = Rectangle{width=123, height=234}
s = Shape.rectangle(rect)
test.assert(s.!rectangle == rect)

--cytest: pass
16 changes: 16 additions & 0 deletions test/types/choice_unwrap_panic.cy
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use test

type Choice enum:
case a int
case b String

var c = Choice.a(123)
c.!b

--cytest: error
--panic: Expected active choice tag `1`, found `0`.
--
--main:8:1 main:
--c.!b
--^
--

0 comments on commit 6819984

Please sign in to comment.