Skip to content

Commit

Permalink
Implement optional types.
Browse files Browse the repository at this point in the history
  • Loading branch information
fubark committed Feb 12, 2024
1 parent de8f451 commit 77a402d
Show file tree
Hide file tree
Showing 23 changed files with 402 additions and 61 deletions.
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ pub fn buildCVM(b: *std.Build, opts: Options) !*std.build.Step.Compile {
.target = opts.target,
.optimize = opts.optimize,
});
lib.linkLibC();

var cflags = std.ArrayList([]const u8).init(b.allocator);
if (opts.optimize == .Debug) {
Expand Down
86 changes: 83 additions & 3 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ print int(currency) -- '123' or some arbitrary id.
* [Type functions.](#type-functions)
* [Type variables.](#type-variables)
* [Structs.](#structs)
* [Declare struct type.](#declare-struct-type)
* [Declare struct.](#declare-struct)
* [Copy structs.](#copy-structs)
* [Enums.](#enums)
* [Choices.](#choices)
Expand All @@ -807,6 +807,13 @@ print int(currency) -- '123' or some arbitrary id.
</td><td valign="top">

* [Optionals.](#optionals)
* [Wrap value.](#wrap-value)
* [Wrap `none`.](#wrap-none)
* [Unwrap or panic.](#unwrap-or-panic)
* [Unwrap or default.](#unwrap-or-default)
* [Optional chaining.](#optional-chaining)
* [`if` unwrap.](#if-unwrap)
* [`while` unwrap.](#while-unwrap)
* [Type aliases.](#type-aliases)
* [Traits.](#traits)
* [Union types.](#union-types)
Expand Down Expand Up @@ -971,7 +978,7 @@ Struct types can contain field and method members just like object types, but th

Unlike objects, structs do not have a reference count. They can be safely referenced using borrow semantics. Unsafe pointers can also reference structs.

### Declare struct type.
### Declare struct.
Struct types are created using the `type struct` declaration:
```cy
type Vec2 struct:
Expand Down Expand Up @@ -1076,7 +1083,80 @@ print s.!line -- Prints '20'
```

## Optionals.
> _Planned Feature_
The generic `Option` type is a choice type that either holds a `none` value or contains `some` value. The option template is defined as:
```cy
template(T type)
type Option enum:
case none
case some #T
```
A type prefixed with `?` is a more idiomatic way to create an option type. The following String optional types are equivalent:
```cy
Option#String
?String
```

### Wrap value.
Use `?` as a type prefix to turn it into an `Option` type. A value is automatically wrapped into the inferred optional's `some` case:
```cy
var a ?String = 'abc'
print a -- Prints 'Option(abc)'
```

### Wrap `none`.
`none` is automatically initialized to the inferred optional's `none` case:
```cy
var a ?String = none
print a -- Prints 'Option.none'
```

### Unwrap or panic.
The `.?` access operator is used to unwrap an optional. If the expression evaluates to the `none` case, the runtime panics:
```cy
var opt ?String = 'abc'
var v = opt.?
print v -- Prints 'abc'
```

### Unwrap or default.
The `?else` control flow operator either returns the unwrapped value or a default value when the optional is `none`: *Planned Feature*
```cy
var opt ?String = none
var v = opt ?else 'empty'
print v -- Prints 'empty'
```

`else` can also start a new statement block: *Planned Feature*
```cy
var v = opt else:
break 'empty'

var v = opt else:
throw error.Missing
```

### Optional chaining.
Given the last member's type `T` in a chain of `?.` access operators, the chain's execution will either return `Option(T).none` on the first encounter of `none` or returns the last member as an `Option(T).some`: *Planned Feature*
```cy
print root?.a?.b?.c?.last
```

### `if` unwrap.
The `if` statement can be amended to unwrap an optional value using the capture `->` operator: *Planned Feature*
```cy
var opt ?String = 'abc'
if opt -> v:
print v -- Prints 'abc'
```

### `while` unwrap.
The `while` statement can be amended to unwrap an optional value using the capture `->` operator.
The loop exits when `none` is encountered: *Planned Feature*
```cy
var iter = dir.walk()
while iter.next() -> entry:
print entry.name
```

## Type aliases.
A type alias is declared from a single line `type` statement. This creates a new type symbol for an existing data type.
Expand Down
13 changes: 13 additions & 0 deletions src/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub const NodeType = enum(u8) {
enumDecl,
enumMember,
errorSymLit,
expandOpt,
exprStmt,
falseLit,
forIterHeader,
Expand Down Expand Up @@ -93,6 +94,8 @@ pub const NodeType = enum(u8) {
typeAliasDecl,
typeTemplate,
unary_expr,
unwrap,
unwrapDef,
varSpec,
whileCondStmt,
whileInfStmt,
Expand Down Expand Up @@ -131,6 +134,9 @@ const NodeHead = packed struct {
/// At most 16 bytes in release mode.
const NodeData = union {
uninit: void,
expandOpt: struct {
param: NodeId,
},
exprStmt: struct {
child: NodeId,
isLastRootStmt: bool = false,
Expand Down Expand Up @@ -238,6 +244,13 @@ const NodeData = union {
left: NodeId,
right: NodeId,
},
unwrap: struct {
opt: NodeId,
},
unwrapDef: struct {
opt: NodeId,
default: NodeId,
},
callExpr: packed struct {
callee: u24,
numArgs: u8,
Expand Down
16 changes: 16 additions & 0 deletions src/bc_gen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ fn genExpr(c: *Chunk, idx: usize, cstr: Cstr) anyerror!GenValue {
.truev => genTrue(c, cstr, nodeId),
.tryExpr => genTryExpr(c, idx, cstr, nodeId),
.typeSym => genTypeSym(c, idx, cstr, nodeId),
.unwrapChoice => genUnwrapChoice(c, idx, cstr, nodeId),
.varSym => genVarSym(c, idx, cstr, nodeId),
.blockExpr => genBlockExpr(c, idx, cstr, nodeId),
else => {
Expand Down Expand Up @@ -1032,6 +1033,21 @@ fn genSymbol(c: *Chunk, idx: usize, cstr: Cstr, nodeId: cy.NodeId) !GenValue {
return finishNoErrNoDepInst(c, inst, false);
}

fn genUnwrapChoice(c: *Chunk, loc: usize, cstr: Cstr, nodeId: cy.NodeId) !GenValue {
const data = c.ir.getExprData(loc, .unwrapChoice);
const retain = c.sema.isRcCandidateType(data.payload_t);
const inst = try c.rega.selectForDstInst(cstr, retain, nodeId);

const choice = try genExpr(c, data.choice, Cstr.simple);
try pushUnwindValue(c, choice);

try c.pushFCode(.unwrapChoice, &.{ choice.reg, data.tag, data.fieldIdx, inst.dst }, nodeId);

try popTempAndUnwind(c, choice);
try releaseTempValue(c, choice, nodeId);
return finishDstInst(c, inst, retain);
}

fn genTrue(c: *Chunk, cstr: Cstr, nodeId: cy.NodeId) !GenValue {
const inst = try c.rega.selectForNoErrNoDepInst(cstr, false, nodeId);
if (inst.requiresPreRelease) {
Expand Down
7 changes: 6 additions & 1 deletion src/builtins/builtins.cy
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,9 @@ type Fiber:

#host
type metatype:
#host func id() int
#host func id() int

template(T type)
type Option enum:
case none
case some #T
7 changes: 7 additions & 0 deletions src/builtins/builtins.zig
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,9 @@ fn genDeclEntry(vm: *cy.VM, ast: cy.ast.AstView, decl: cy.parser.StaticDecl, sta
const header = ast.node(typeDecl.data.objectDecl.header);
name = ast.nodeStringById(header.data.objectHeader.name);
},
.enumDecl => {
name = ast.nodeStringById(typeDecl.data.enumDecl.name);
},
else => {
return error.Unsupported;
},
Expand Down Expand Up @@ -743,9 +746,13 @@ fn genDocComment(vm: *cy.VM, ast: cy.ast.AstView, decl: cy.parser.StaticDecl, st

fn parseCyberGenResult(vm: *cy.VM, parser: *const cy.Parser) !Value {
const root = try vm.allocEmptyMap();
errdefer vm.release(root);

const map = root.asHeapObject().map.map();

const decls = try vm.allocEmptyList();
errdefer vm.release(decls);

const declsList = decls.asHeapObject().list.getList();

var state = ParseCyberState{
Expand Down
4 changes: 3 additions & 1 deletion src/bytecode.zig
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ pub fn getInstLenAt(pc: [*]const Inst) u8 {
const numEntries = pc[2].val;
return 4 + numEntries * 2;
},
.unwrapChoice,
.cast,
.castAbstract,
.pushTry,
Expand Down Expand Up @@ -1187,6 +1188,7 @@ pub const OpCode = enum(u8) {
ref = vmc.CodeRef,
refCopyObj = vmc.CodeRefCopyObj,
setRef = vmc.CodeSetRef,
unwrapChoice = vmc.CodeUnwrapChoice,

setFieldDyn = vmc.CodeSetFieldDyn,
setFieldDynIC = vmc.CodeSetFieldDynIC,
Expand Down Expand Up @@ -1273,7 +1275,7 @@ pub const OpCode = enum(u8) {
};

test "bytecode internals." {
try t.eq(std.enums.values(OpCode).len, 118);
try t.eq(std.enums.values(OpCode).len, 119);
try t.eq(@sizeOf(Inst), 1);
if (cy.is32Bit) {
try t.eq(@sizeOf(DebugMarker), 16);
Expand Down
53 changes: 25 additions & 28 deletions src/cte.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,7 @@ const v = cy.fmt.v;

const cte = @This();

pub fn callTemplate(c: *cy.Chunk, nodeId: cy.NodeId) !*cy.Sym {
const node = c.ast.node(nodeId);
const callee = node.data.callTemplate.callee;
const numArgs = node.data.callTemplate.numArgs;
_ = numArgs;

const calleeRes = try c.semaExprSkipSym(node.data.callTemplate.callee);
if (calleeRes.resType != .sym) {
return c.reportErrorAt("Expected template symbol.", &.{}, node.data.callTemplate.callee);
}
const sym = calleeRes.data.sym;
if (sym.type != .typeTemplate) {
return c.reportErrorAt("Expected template symbol.", &.{}, node.data.callTemplate.callee);
}

pub fn callTemplate2(c: *cy.Chunk, template: *cy.sym.TypeTemplate, argHead: cy.NodeId, nodeId: cy.NodeId) !*cy.Sym {
// Accumulate compile-time args.
const typeStart = c.typeStack.items.len;
const valueStart = c.valueStack.items.len;
Expand All @@ -34,7 +20,7 @@ pub fn callTemplate(c: *cy.Chunk, nodeId: cy.NodeId) !*cy.Sym {
}
c.valueStack.items.len = valueStart;
}
var arg: cy.NodeId = node.data.callTemplate.argHead;
var arg: cy.NodeId = argHead;
while (arg != cy.NullNode) {
const res = try nodeToCtValue(c, arg);
try c.typeStack.append(c.alloc, res.type);
Expand All @@ -45,20 +31,18 @@ pub fn callTemplate(c: *cy.Chunk, nodeId: cy.NodeId) !*cy.Sym {
const args = c.valueStack.items[valueStart..];

// Check against template signature.
const typeTemplate = sym.cast(.typeTemplate);

if (!cy.types.isTypeFuncSigCompat(c.compiler, @ptrCast(argTypes), bt.Type, typeTemplate.sigId)) {
const expSig = try c.sema.allocFuncSigStr(typeTemplate.sigId);
if (!cy.types.isTypeFuncSigCompat(c.compiler, @ptrCast(argTypes), bt.Type, template.sigId)) {
const expSig = try c.sema.allocFuncSigStr(template.sigId);
defer c.alloc.free(expSig);
return c.reportErrorAt(
\\Expected template signature `{}{}`.
, &.{v(typeTemplate.head.name()), v(expSig)}, callee);
, &.{v(template.head.name()), v(expSig)}, nodeId);
}

// Ensure variant type.
const res = try typeTemplate.variantCache.getOrPut(c.alloc, args);
const res = try template.variantCache.getOrPut(c.alloc, args);
if (!res.found_existing) {
const patchNodes = try execTemplateCtNodes(c, typeTemplate, args);
const patchNodes = try execTemplateCtNodes(c, template, args);

// Dupe args and retain
const params = try c.alloc.dupe(cy.Value, args);
Expand All @@ -67,25 +51,38 @@ pub fn callTemplate(c: *cy.Chunk, nodeId: cy.NodeId) !*cy.Sym {
}

// Generate variant type.
const id = typeTemplate.variants.items.len;
try typeTemplate.variants.append(c.alloc, .{
const id = template.variants.items.len;
try template.variants.append(c.alloc, .{
.patchNodes = patchNodes,
.params = params,
.sym = undefined,
});

const newSym = try sema.declareTemplateVariant(c, typeTemplate, @intCast(id));
typeTemplate.variants.items[id].sym = newSym;
const newSym = try sema.declareTemplateVariant(c, template, @intCast(id));
template.variants.items[id].sym = newSym;

res.key_ptr.* = params;
res.value_ptr.* = @intCast(id);
}

const variantId = res.value_ptr.*;
const variantSym = typeTemplate.variants.items[variantId].sym;
const variantSym = template.variants.items[variantId].sym;
return variantSym;
}

pub fn callTemplate(c: *cy.Chunk, nodeId: cy.NodeId) !*cy.Sym {
const node = c.ast.node(nodeId);
const calleeRes = try c.semaExprSkipSym(node.data.callTemplate.callee);
if (calleeRes.resType != .sym) {
return c.reportErrorAt("Expected template symbol.", &.{}, node.data.callTemplate.callee);
}
const sym = calleeRes.data.sym;
if (sym.type != .typeTemplate) {
return c.reportErrorAt("Expected template symbol.", &.{}, node.data.callTemplate.callee);
}
return cte.callTemplate2(c, sym.cast(.typeTemplate), node.data.callTemplate.argHead, nodeId);
}

/// Visit each top level ctNode, perform template param substitution or CTE,
/// and generate new nodes. Return the root of each resulting node.
fn execTemplateCtNodes(c: *cy.Chunk, template: *cy.sym.TypeTemplate, params: []const cy.Value) ![]const cy.NodeId {
Expand Down
9 changes: 9 additions & 0 deletions src/ir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ pub const ExprCode = enum(u8) {
blockExpr,
mainEnd,
elseBlock,
unwrapChoice,
};

pub const UnwrapChoice = struct {
choice: Loc,
payload_t: cy.TypeId,
tag: u8,
fieldIdx: u8,
};

pub const Coresume = struct {
Expand Down Expand Up @@ -613,6 +621,7 @@ pub fn ExprData(comptime code: ExprCode) type {
.symbol => Symbol,
.blockExpr => BlockExpr,
.coresume => Coresume,
.unwrapChoice => UnwrapChoice,
else => void,
};
}
Expand Down
Loading

0 comments on commit 77a402d

Please sign in to comment.