Skip to content

Commit

Permalink
Object initializer type checks and inference.
Browse files Browse the repository at this point in the history
  • Loading branch information
fubark committed Oct 3, 2023
1 parent dc40940 commit a516acc
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 133 deletions.
14 changes: 10 additions & 4 deletions src/builtins/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2026,11 +2026,11 @@ pub const ModuleBuilder = struct {
}

pub fn setVar(self: *const ModuleBuilder, name: []const u8, typeSymId: sema.SymbolId, val: Value) !void {
try self.mod().setTypedVar(self.compiler, name, typeSymId, val);
try self.getMod().setTypedVar(self.compiler, name, typeSymId, val);
}

pub fn setFunc(self: *const ModuleBuilder, name: []const u8, params: []const sema.SymbolId, ret: sema.SymbolId, ptr: cy.ZHostFuncFn) !void {
try self.mod().setNativeTypedFunc(self.compiler, name, params, ret, ptr);
try self.getMod().setNativeTypedFunc(self.compiler, name, params, ret, ptr);
}

pub fn ensureMethodGroup(self: *const ModuleBuilder, name: []const u8) !vmc.MethodGroupId {
Expand Down Expand Up @@ -2062,15 +2062,15 @@ pub const ModuleBuilder = struct {
}
}

pub fn mod(self: *const ModuleBuilder) *cy.Module {
pub fn getMod(self: *const ModuleBuilder) *cy.Module {
return self.compiler.sema.getModulePtr(self.modId);
}

pub fn createAndSetTypeObject(self: *const ModuleBuilder, name: []const u8, fields: []const []const u8) !rt.TypeId {
const nameId = try cy.sema.ensureNameSym(self.compiler, name);
const key = sema.ResolvedSymKey{
.resolvedSymKey = .{
.parentSymId = self.mod().resolvedRootSymId,
.parentSymId = self.getMod().resolvedRootSymId,
.nameId = nameId,
},
};
Expand All @@ -2079,10 +2079,16 @@ pub const ModuleBuilder = struct {
const sym = self.compiler.sema.getSymbol(res.sTypeId);
const typeId = sym.inner.object.typeId;

const mod = self.compiler.sema.getModulePtr(modId);
const modFields = try self.compiler.alloc.alloc(cy.module.FieldInfo, fields.len);

for (fields, 0..) |field, i| {
const id = try self.vm.ensureFieldSym(field);
try self.vm.addFieldSym(typeId, id, @intCast(i), bt.Any);
const fieldNameId = try cy.sema.ensureNameSym(self.compiler, field);
try cy.module.setField(mod, self.compiler.alloc, fieldNameId, @intCast(i), bt.Any);
}
mod.fields = modFields;
self.vm.types.buf[typeId].data.numFields = @intCast(fields.len);
return typeId;
}
Expand Down
19 changes: 7 additions & 12 deletions src/chunk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub const Chunk = struct {
parser: cy.Parser,
parserAstRootId: cy.NodeId,

/// Generic linked list buffer.
dataNodes: std.ArrayListUnmanaged(DataNode),
/// Generic stack data that is untouched by block pre/post tasks.
stackData: std.ArrayListUnmanaged(GenericItem),

/// Used for temp string building.
tempBufU8: std.ArrayListUnmanaged(u8),
Expand Down Expand Up @@ -183,7 +183,7 @@ pub const Chunk = struct {
.curSemaInitingSym = @bitCast(@as(u32, cy.NullId)),
.semaVarDeclDeps = .{},
.bufU32 = .{},
.dataNodes = .{},
.stackData = .{},
.tempBufU8 = .{},
.srcOwned = false,
.modId = cy.NullId,
Expand Down Expand Up @@ -237,7 +237,7 @@ pub const Chunk = struct {

self.bufU32.deinit(self.alloc);
self.semaVarDeclDeps.deinit(self.alloc);
self.dataNodes.deinit(self.alloc);
self.stackData.deinit(self.alloc);

self.blockJumpStack.deinit(self.alloc);
self.subBlockJumpStack.deinit(self.alloc);
Expand Down Expand Up @@ -887,17 +887,12 @@ pub const Chunk = struct {
}
};

const DataNode = extern struct {
inner: extern union {
funcSym: extern struct {
symId: u32,
},
},
next: u32,
const GenericItem = extern union {
nodeId: cy.NodeId,
};

test "chunk internals." {
try t.eq(@sizeOf(DataNode), 8);
try t.eq(@sizeOf(GenericItem), 4);
}

const GenBlock = struct {
Expand Down
44 changes: 17 additions & 27 deletions src/codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -253,65 +253,55 @@ fn objectInit(self: *Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue {
if (node.head.objectInit.sema_symId != cy.NullId) {
const rSym = self.compiler.sema.getSymbol(node.head.objectInit.sema_symId);
if (rSym.symT == .object) {
const typeId = rSym.getObjectTypeId(self.compiler.vm).?;
const initializer = self.nodes[node.head.objectInit.initializer];

// TODO: Would it be faster/efficient to copy the fields into contiguous registers
// and copy all at once to heap or pass locals into the operands and iterate each and copy to heap?
// The current implementation is the former.
// TODO: Have sema sort the fields so eval can handle default values easier.

// Push props onto stack.
const mod = self.compiler.sema.getModule(rSym.inner.object.modId);

const numFields = self.compiler.vm.types.buf[typeId].data.numFields;
// Repurpose stack for sorting fields.
const sortedFieldsStart = self.assignedVarStack.items.len;
try self.assignedVarStack.resize(self.alloc, self.assignedVarStack.items.len + numFields);
defer self.assignedVarStack.items.len = sortedFieldsStart;
// TODO: This is one case where having an IR would be helpful
// to avoid initializing this buffer again.
const fieldsDataStart = self.stackData.items.len;
try self.stackData.resize(self.alloc, self.stackData.items.len + mod.fields.len);
defer self.stackData.items.len = fieldsDataStart;

// Initially set to NullId so leftovers are defaulted to `none`.
const initFields = self.assignedVarStack.items[sortedFieldsStart..];
@memset(initFields, cy.NullId);
const fieldsData = self.stackData.items[fieldsDataStart..];
@memset(fieldsData, .{ .nodeId = cy.NullId });

const dst = try self.rega.selectFromNonLocalVar(req, true);
const tempStart = self.rega.getNextTemp();
defer self.rega.setNextTemp(tempStart);

var i: u32 = 0;
var entryId = initializer.head.child_head;
// First iteration to sort the initializer fields.

while (entryId != cy.NullId) : (i += 1) {
const entry = self.nodes[entryId];
const prop = self.nodes[entry.head.mapEntry.left];
const fieldName = self.getNodeTokenString(prop);
const fieldIdx = self.compiler.vm.getStructFieldIdx(typeId, fieldName) orelse {
const vmType = self.compiler.vm.types.buf[typeId];
const objectName = vmType.namePtr[0..vmType.nameLen];
return self.reportErrorAt("Missing field `{}` in `{}`.", &.{v(fieldName), v(objectName)}, entry.head.mapEntry.left);
};
initFields[fieldIdx] = entryId;
fieldsData[entry.head.mapEntry.semaFieldIdx] = .{ .nodeId = entryId };
entryId = entry.next;
}

i = 0;
while (i < numFields) : (i += 1) {
entryId = self.assignedVarStack.items[sortedFieldsStart + i];
if (entryId == cy.NullId) {
for (fieldsData) |item| {
if (item.nodeId == cy.NullId) {
// Push none.
const local = try self.rega.consumeNextTemp();
try self.buf.pushOp1(.none, local);
} else {
const entry = self.nodes[entryId];
const entry = self.nodes[item.nodeId];
_ = try expression(self, entry.head.mapEntry.right, RegisterCstr.tempMustRetain);
}
}

if (self.compiler.vm.types.buf[typeId].data.numFields <= 4) {
const typeId = rSym.getObjectTypeId(self.compiler.vm).?;
if (mod.fields.len <= 4) {
try self.pushOptionalDebugSym(nodeId);
try self.buf.pushOpSlice(.objectSmall, &[_]u8{ @intCast(typeId), tempStart, @intCast(numFields), dst });
try self.buf.pushOpSlice(.objectSmall, &[_]u8{ @intCast(typeId), tempStart, @intCast(mod.fields.len), dst });
} else {
try self.pushDebugSym(nodeId);
try self.buf.pushOpSlice(.object, &[_]u8{ @intCast(typeId), tempStart, @intCast(numFields), dst });
try self.buf.pushOpSlice(.object, &[_]u8{ @intCast(typeId), tempStart, @intCast(mod.fields.len), dst });
}
const type_ = node.head.objectInit.sema_symId;
return GenValue.initTempValue(dst, type_, true);
Expand Down
48 changes: 42 additions & 6 deletions src/module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub const Module = struct {
/// If this is a submodule, it is a relative name.
absSpec: []const u8,

/// For object module.
fields: []const FieldInfo = &.{},

pub fn setNativeTypedFunc(self: *Module, c: *cy.VMcompiler, name: []const u8,
sig: []const sema.SymbolId, retSymId: sema.SymbolId, func: cy.ZHostFuncFn) !void {
return self.setNativeTypedFuncExt(c, name, false, sig, retSymId, func);
Expand Down Expand Up @@ -297,6 +300,7 @@ pub const Module = struct {
}
}
self.syms.deinit(alloc);
alloc.free(self.fields);
alloc.free(self.absSpec);
}

Expand Down Expand Up @@ -331,6 +335,8 @@ const ModuleSymType = enum {
enumMember,
userObject,
typeAlias,

field,
};

const ModuleSym = struct {
Expand Down Expand Up @@ -394,6 +400,10 @@ const ModuleSym = struct {
userObject: struct {
declId: cy.NodeId,
},
field: struct {
idx: u32,
typeId: types.TypeId,
},
},
};

Expand All @@ -402,14 +412,37 @@ const ModuleFuncNode = struct {
funcSigId: sema.FuncSigId,
};

pub const FieldInfo = packed struct {
nameId: u31,
required: bool,
};

pub fn getSym(mod: *const Module, nameId: sema.NameSymId) ?ModuleSym {
const key = ModuleSymKey.initModuleSymKey(nameId, null);
return mod.syms.get(key);
}

pub fn symNameExists(mod: *const Module, nameId: sema.NameSymId) bool {
const key = ModuleSymKey.initModuleSymKey(nameId, null);
return mod.syms.contains(key);
}

pub fn setField(mod: *Module, alloc: std.mem.Allocator, nameId: sema.NameSymId, idx: u32, typeId: types.TypeId) !void {
const key = ModuleSymKey.initModuleSymKey(nameId, null);
try mod.syms.put(alloc, key, .{
.symT = .field,
.inner = .{
.field = .{
.idx = idx,
.typeId = typeId,
},
},
});
}

pub fn declareTypeObject(c: *cy.VMcompiler, modId: ModuleId, name: []const u8, chunkId: cy.ChunkId, declId: cy.NodeId) !ModuleId {
const nameId = try sema.ensureNameSym(c, name);
const key = ModuleSymKey{
.moduleSymKey = .{
.nameId = nameId,
.funcSigId = cy.NullId,
},
};
const key = ModuleSymKey.initModuleSymKey(nameId, null);
if (c.sema.getModule(modId).syms.contains(key)) {
return error.DuplicateSymName;
}
Expand Down Expand Up @@ -591,6 +624,9 @@ pub fn findDistinctModuleSym(chunk: *cy.Chunk, modId: ModuleId, nameId: sema.Nam
const name = sema.getName(chunk.compiler, nameId);
return chunk.reportError("Symbol `{}` is ambiguous. There are multiple functions with the same name.", &.{v(name)});
},
.field => {
return false;
},
}
}
return false;
Expand Down
15 changes: 11 additions & 4 deletions src/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -752,12 +752,13 @@ pub const Parser = struct {
return id;
}

fn pushObjectDecl(self: *Parser, start: TokenId, name: NodeId, modifierHead: NodeId, fieldsHead: NodeId, funcsHead: NodeId) !NodeId {
fn pushObjectDecl(self: *Parser, start: TokenId, name: NodeId, modifierHead: NodeId, fieldsHead: NodeId, numFields: u32, funcsHead: NodeId) !NodeId {
const body = try self.pushNode(.objectDeclBody, start);
self.nodes.items[body].head = .{
.objectDeclBody = .{
.fieldsHead = fieldsHead,
.funcsHead = funcsHead,
.numFields = numFields,
},
};

Expand Down Expand Up @@ -801,21 +802,23 @@ pub const Parser = struct {
defer self.cur_indent = prevIndent;

var firstField = (try self.parseObjectField()) orelse NullId;
var numFields: u32 = 1;
if (firstField != NullId) {
var lastField = firstField;

while (true) {
const start2 = self.next_pos;
const indent = (try self.consumeIndentBeforeStmt()) orelse {
return self.pushObjectDecl(start, name, modifierHead, firstField, NullId);
return self.pushObjectDecl(start, name, modifierHead, firstField, numFields, NullId);
};
if (indent == reqIndent) {
const id = (try self.parseObjectField()) orelse break;
numFields += 1;
self.nodes.items[lastField].next = id;
lastField = id;
} else if (try isRecedingIndent(self, prevIndent, reqIndent, indent)) {
self.next_pos = start2;
return self.pushObjectDecl(start, name, modifierHead, firstField, NullId);
return self.pushObjectDecl(start, name, modifierHead, firstField, numFields, NullId);
} else {
return self.reportParseError("Unexpected indentation.", &.{});
}
Expand Down Expand Up @@ -846,7 +849,7 @@ pub const Parser = struct {
return self.reportParseError("Unexpected indentation.", &.{});
}
}
return self.pushObjectDecl(start, name, modifierHead, firstField, firstFunc);
return self.pushObjectDecl(start, name, modifierHead, firstField, numFields, firstFunc);
} else {
return self.reportParseError("Expected function.", &.{});
}
Expand Down Expand Up @@ -3540,6 +3543,9 @@ pub const Node = struct {
mapEntry: struct {
left: NodeId,
right: NodeId,

// Used for object initializers to map an entry to an object field.
semaFieldIdx: u32 = cy.NullId,
},
caseBlock: struct {
firstCond: NodeId,
Expand Down Expand Up @@ -3636,6 +3642,7 @@ pub const Node = struct {
objectDeclBody: struct {
fieldsHead: NodeId,
funcsHead: NodeId,
numFields: u32,
},
varSpec: struct {
name: NodeId,
Expand Down
Loading

0 comments on commit a516acc

Please sign in to comment.