Skip to content

Commit

Permalink
feat: const object fields
Browse files Browse the repository at this point in the history
Allow to mark an object property as `const`.
If all properties of an object are `const` then the object is considered itself `const` and can be used as enum values.

closes #13
  • Loading branch information
giann committed May 31, 2024
1 parent 188b2c5 commit 0b9158c
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 220 deletions.
120 changes: 94 additions & 26 deletions src/Codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,67 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.
self.reporter.reportPlaceholder(self.ast, value_type_def.resolved_type.?.Placeholder);
}

// Type check value
const field_name = self.ast.tokens.items(.lexeme)[components.identifier];
switch (callee_type.def_type) {
.ForeignContainer => {
const field_type = callee_type.resolved_type.?.ForeignContainer.buzz_type.get(field_name).?;

if (!field_type.eql(value_type_def)) {
self.reporter.reportTypeCheck(
.assignment_value_type,
callee_type.resolved_type.?.ForeignContainer.location,
field_type,
self.ast.tokens.get(locations[value]),
value_type_def,
"Bad property type",
);
}
},
.ObjectInstance, .Object => {
const field = if (callee_type.def_type == .ObjectInstance)
callee_type.resolved_type.?.ObjectInstance
.resolved_type.?.Object.fields.get(field_name).?
else
callee_type.resolved_type.?.Object.fields.get(field_name).?;

if (field.method or
(callee_type.def_type == .ObjectInstance and field.static) or
(callee_type.def_type == .Object and !field.static))
{
self.reporter.reportErrorFmt(
.assignable,
self.ast.tokens.get(locations[value]),
"`{s}` is not assignable",
.{
field_name,
},
);
} else if (field.constant) {
self.reporter.reportErrorFmt(
.constant_property,
self.ast.tokens.get(locations[value]),
"`{s}` is constant",
.{
field_name,
},
);
}

if (!field.type_def.eql(value_type_def)) {
self.reporter.reportTypeCheck(
.assignment_value_type,
field.location,
field.type_def,
self.ast.tokens.get(locations[value]),
value_type_def,
"Bad property type",
);
}
},
else => unreachable,
}

_ = try self.generateNode(value, breaks);

try self.emitCodeArg(
Expand Down Expand Up @@ -2880,7 +2941,8 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks
const member_name_constant = try self.identifierConstant(member_name);

if (member.method) {
const member_type_def = object_def.methods.get(member_name) orelse object_def.static_fields.get(member_name).?;
const member_field = object_def.fields.get(member_name).?;
const member_type_def = member_field.type_def;

if (member_type_def.def_type == .Placeholder) {
self.reporter.reportPlaceholder(self.ast, member_type_def.resolved_type.?.Placeholder);
Expand Down Expand Up @@ -2928,45 +2990,41 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks
}
}

const is_static = object_def.static_fields.get(member_name) != null;

_ = try self.generateNode(member.method_or_default_value.?, breaks);
try self.emitCodeArg(
location,
if (is_static) .OP_PROPERTY else .OP_METHOD,
if (member_field.static) .OP_PROPERTY else .OP_METHOD,
member_name_constant,
);
} else {
// Properties
const property_type = object_def.fields.get(member_name) orelse object_def.static_fields.get(member_name);
const is_static = object_def.static_fields.get(member_name) != null;

std.debug.assert(property_type != null);
const property_field = object_def.fields.get(member_name).?;
const property_type = property_field.type_def;

// Create property default value
if (member.method_or_default_value) |default| {
const default_type_def = type_defs[default].?;
if (default_type_def.def_type == .Placeholder) {
self.reporter.reportPlaceholder(self.ast, default_type_def.resolved_type.?.Placeholder);
} else if (!property_type.?.eql(default_type_def)) {
} else if (!property_type.eql(default_type_def)) {
self.reporter.reportTypeCheck(
.property_default_value,
object_def.location,
property_type.?,
property_type,
self.ast.tokens.get(locations[default]),
default_type_def,
"Wrong property default value type",
);
}

if (is_static) {
if (property_field.static) {
try self.emitOpCode(location, .OP_COPY);
}

_ = try self.generateNode(default, breaks);

// Create property default value
if (is_static) {
if (property_field.static) {
try self.emitCodeArg(location, .OP_SET_OBJECT_PROPERTY, member_name_constant);
try self.emitOpCode(location, .OP_POP);
} else {
Expand Down Expand Up @@ -3023,21 +3081,28 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error
.OP_FCONTAINER_INSTANCE,
);

const fields = if (node_type_def.def_type == .ObjectInstance)
node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields
else
node_type_def.resolved_type.?.ForeignContainer.buzz_type;
var fields = if (node_type_def.def_type == .ObjectInstance) inst: {
const fields = node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields;
var fields_type_defs = std.StringArrayHashMap(*obj.ObjTypeDef).init(self.gc.allocator);
var it = fields.iterator();
while (it.next()) |kv| {
try fields_type_defs.put(
kv.value_ptr.*.name,
kv.value_ptr.*.type_def,
);
}
break :inst fields_type_defs;
} else node_type_def.resolved_type.?.ForeignContainer.buzz_type;

defer if (node_type_def.def_type == .ObjectInstance) {
fields.deinit();
};

const object_location = if (node_type_def.def_type == .ObjectInstance)
node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.location
else
node_type_def.resolved_type.?.ForeignContainer.location;

const fields_location = if (node_type_def.def_type == .ObjectInstance)
node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields_locations
else
null;

// Keep track of what's been initialized or not by this statement
var init_properties = std.StringHashMap(void).init(self.gc.allocator);
defer init_properties.deinit();
Expand Down Expand Up @@ -3066,8 +3131,8 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error
}
self.reporter.reportTypeCheck(
.property_type,
if (fields_location) |floc|
floc.get(property_name)
if (node_type_def.def_type == .ObjectInstance)
node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields.get(property_name).?.location
else
object_location,
prop,
Expand Down Expand Up @@ -3105,15 +3170,18 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error
// Did we initialized all properties without a default value?
// If union we're statisfied with only on field initialized
if (node_type_def.def_type != .ForeignContainer or node_type_def.resolved_type.?.ForeignContainer.zig_type != .Union or init_properties.count() == 0) {
const fields_defaults = if (node_type_def.def_type == .ObjectInstance)
node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields_defaults
const field_defs = if (node_type_def.def_type == .ObjectInstance)
node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields
else
null;

var it = fields.iterator();
while (it.next()) |kv| {
const field = if (field_defs) |fd| fd.get(kv.key_ptr.*) else null;
// If ommitted in initialization and doesn't have default value
if (init_properties.get(kv.key_ptr.*) == null and (fields_defaults == null or fields_defaults.?.get(kv.key_ptr.*) == null)) {
if (init_properties.get(kv.key_ptr.*) == null and
(field == null or (!field.?.has_default and !field.?.method and !field.?.static)))
{
self.reporter.reportErrorFmt(
.property_not_initialized,
self.ast.tokens.get(location),
Expand Down
113 changes: 58 additions & 55 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1851,11 +1851,11 @@ fn resolvePlaceholderWithRelation(
// Search for a field matching the placeholder
if (object_def.fields.get(child_placeholder.name.?.string)) |field| {
// TODO: remove? should only resolve with a field if field accessing an object instance?
try self.resolvePlaceholder(child, field, false);
} else if (object_def.methods.get(child_placeholder.name.?.string)) |method_def| {
try self.resolvePlaceholder(child, method_def, true);
} else if (object_def.static_fields.get(child_placeholder.name.?.string)) |static_def| {
try self.resolvePlaceholder(child, static_def, false);
try self.resolvePlaceholder(
child,
field.type_def,
field.constant,
);
} else {
self.reportErrorFmt(
.property_does_not_exists,
Expand All @@ -1875,9 +1875,11 @@ fn resolvePlaceholderWithRelation(

// Search for a field matching the placeholder
if (object_def.fields.get(child_placeholder.name.?.string)) |field| {
try self.resolvePlaceholder(child, field, false);
} else if (object_def.methods.get(child_placeholder.name.?.string)) |method_def| {
try self.resolvePlaceholder(child, method_def, true);
try self.resolvePlaceholder(
child,
field.type_def,
field.constant,
);
} else {
self.reportErrorFmt(
.property_does_not_exists,
Expand Down Expand Up @@ -2990,6 +2992,7 @@ fn parseObjType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString
while (!self.check(.RightBrace) and !self.check(.Eof)) {
const property_type = try self.parseTypeDef(generic_types, true);

const constant = try self.match(.Const);
try self.consume(.Identifier, "Expected property name.");
const property_name = self.current_token.? - 1;
const property_name_lexeme = self.ast.tokens.items(.lexeme)[property_name];
Expand All @@ -3003,7 +3006,15 @@ fn parseObjType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString
}
try object_type.resolved_type.?.Object.fields.put(
property_name_lexeme,
self.ast.nodes.items(.type_def)[property_type].?,
.{
.name = property_name_lexeme,
.type_def = self.ast.nodes.items(.type_def)[property_type].?,
.location = self.ast.tokens.get(property_name),
.constant = constant,
.static = false,
.method = false,
.has_default = false,
},
);
try field_names.put(property_name_lexeme, {});
try fields.append(
Expand Down Expand Up @@ -3874,7 +3885,15 @@ fn anonymousObjectInit(self: *Self, _: bool) Error!Ast.Node.Index {
);
try object_type.resolved_type.?.Object.fields.put(
property_name_lexeme,
self.ast.nodes.items(.type_def)[expr].?,
.{
.name = property_name_lexeme,
.type_def = self.ast.nodes.items(.type_def)[expr].?,
.location = self.ast.tokens.get(property_name),
.static = false,
.method = false,
.constant = false,
.has_default = false,
},
);

if (!self.check(.RightBrace) or self.check(.Comma)) {
Expand Down Expand Up @@ -4098,7 +4117,8 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind
},
.Object => {
const obj_def: obj.ObjObject.ObjectDef = callee_type_def.?.resolved_type.?.Object;
var property_type = obj_def.static_fields.get(member_name) orelse obj_def.static_placeholders.get(member_name);
const property_field = obj_def.fields.get(member_name);
var property_type = if (property_field) |field| field.type_def else null;

// Not found, create a placeholder, this is a root placeholder not linked to anything
// TODO: test with something else than a name
Expand Down Expand Up @@ -4214,7 +4234,8 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind
const object = callee_type_def.?.resolved_type.?.ObjectInstance;
const obj_def = object.resolved_type.?.Object;

var property_type = obj_def.methods.get(member_name) orelse obj_def.fields.get(member_name) orelse obj_def.placeholders.get(member_name);
const property_field = obj_def.fields.get(member_name);
var property_type = (if (property_field) |field| field.type_def else null) orelse obj_def.placeholders.get(member_name);

// Else create placeholder
if (property_type == null and self.current_object != null and std.mem.eql(u8, self.current_object.?.name.lexeme, obj_def.name.string)) {
Expand Down Expand Up @@ -6222,22 +6243,18 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index {
}
}

if (static) {
try object_type.resolved_type.?.Object.static_fields.put(method_name, method_type_def.?);
try object_type.resolved_type.?.Object.static_fields_locations.put(
method_name,
self.ast.tokens.get(method_token),
);
} else {
try object_type.resolved_type.?.Object.methods.put(
method_name,
method_type_def.?,
);
try object_type.resolved_type.?.Object.methods_locations.put(
method_name,
self.ast.tokens.get(method_token),
);
}
try object_type.resolved_type.?.Object.fields.put(
method_name,
.{
.name = method_name,
.type_def = method_type_def.?,
.constant = true,
.static = static,
.location = self.ast.tokens.get(method_token),
.method = true,
.has_default = false,
},
);

try members.append(
.{
Expand All @@ -6250,8 +6267,7 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index {
try fields.put(method_name, {});
try properties_type.put(method_name, self.ast.nodes.items(.type_def)[method_node].?);
} else {
// TODO: constant object properties
// const constant = try self.match(.Const);
const constant = try self.match(.Const);
const property_type = try self.parseTypeDef(null, true);
const property_type_def = self.ast.nodes.items(.type_def)[property_type];

Expand Down Expand Up @@ -6331,31 +6347,18 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index {
}
}

if (static) {
try object_type.resolved_type.?.Object.static_fields.put(
property_name.lexeme,
self.ast.nodes.items(.type_def)[property_type].?,
);

try object_type.resolved_type.?.Object.static_fields_locations.put(
property_name.lexeme,
property_name,
);
} else {
std.debug.assert(!object_type.optional);
try object_type.resolved_type.?.Object.fields.put(
property_name.lexeme,
self.ast.nodes.items(.type_def)[property_type].?,
);
try object_type.resolved_type.?.Object.fields_locations.put(
property_name.lexeme,
property_name,
);

if (default != null) {
try object_type.resolved_type.?.Object.fields_defaults.put(property_name.lexeme, {});
}
}
try object_type.resolved_type.?.Object.fields.put(
property_name.lexeme,
.{
.name = property_name.lexeme,
.type_def = self.ast.nodes.items(.type_def)[property_type].?,
.constant = constant,
.static = static,
.location = property_name,
.method = false,
.has_default = default != null,
},
);

try members.append(
.{
Expand Down
1 change: 1 addition & 0 deletions src/Reporter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub const Error = enum(u8) {
code_after_return = 93,
unused_import = 94,
label_does_not_exists = 95,
constant_property = 96,
};

// Inspired by https://github.com/zesterer/ariadne
Expand Down
Loading

0 comments on commit 0b9158c

Please sign in to comment.