Skip to content

Commit

Permalink
feat: Infer empty list/map item/key/value type
Browse files Browse the repository at this point in the history
closes #86
  • Loading branch information
giann committed Jan 13, 2025
1 parent 72a1396 commit a6a039c
Show file tree
Hide file tree
Showing 21 changed files with 101 additions and 50 deletions.
8 changes: 4 additions & 4 deletions examples/2048.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object Pair {
}

object Game {
board: [[int]] = [<[int]>],
board: [[int]] = [],
size: int = 4,
goal: int = 2048,
score: int = 0,
Expand All @@ -54,7 +54,7 @@ object Game {
};

foreach (x in 0..game.size) {
game.board.append([<int>]);
game.board.append([]);

foreach (_ in 0..game.size) {
game.board[x].append(0);
Expand Down Expand Up @@ -91,7 +91,7 @@ object Game {

// Return list of free spots
fun freeSpots() > [obj{ x: int, y: int }] {
var spots = [<obj{ x: int, y: int }>];
var spots: [obj{ x: int, y: int }] = [];

foreach (x, row in this.board) {
foreach (y, value in row) {
Expand Down Expand Up @@ -154,7 +154,7 @@ object Game {
}

fun iterate(axis: bool, direction: int) > [Pair] {
var result = [<Pair>];
var result: [Pair] = [];

foreach (x in 0..this.size) {
foreach (y in 0..this.size) {
Expand Down
2 changes: 1 addition & 1 deletion examples/brownian-tree.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fun main(_: [str]) > void !> SDLError {

var canvas: [[bool]] = [];
foreach (i in 0..sizeX) {
canvas.append([<bool>]);
canvas.append([]);

foreach (_ in 0..sizeY) {
canvas[i].append(false);
Expand Down
4 changes: 2 additions & 2 deletions examples/game-of-life.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object World {
var world = World{
width ,
height,
cells = [<bool>],
cells = [],
};

for (i: int = 0; i < width * height; i = i + 1) {
Expand All @@ -36,7 +36,7 @@ object World {
}

fun step() > void {
var cells = [<bool>];
var cells = [];
for (y: int = 0; y < this.height; y = y + 1) {
for (x: int = 0; x < this.width; x = x + 1) {
final coordinate = y * this.width + x;
Expand Down
4 changes: 2 additions & 2 deletions examples/sqlite.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export object Statement {
std\print("Executing query `{this.query}`");
var code: ResultCode = ResultCode.Ok;

final rows = [<[Boxed]>];
final rows: [[Boxed]] = [];
while (code != ResultCode.Done) {
code = ResultCode(sqlite3_step(this.stmt)) ?? ResultCode.Error;
if (code != ResultCode.Ok and code != ResultCode.Row and code != ResultCode.Done) {
Expand All @@ -171,7 +171,7 @@ export object Statement {
}

final columnCount = sqlite3_column_count(this.stmt);
var row = [<any>];
var row: [any] = [];
for (i: int = 0; i < columnCount; i = i + 1) {
final columnType = Type(sqlite3_column_type(this.stmt, col: i)) ?? Type.Null;

Expand Down
6 changes: 3 additions & 3 deletions examples/voronoi-diagram.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ fun hypot(x: int, y: int) > int {
}

fun generateVoronoi(renderer: Renderer, width: int, height: int, numCells: int) > void !> SDLError {
var nx = [<int>];
var ny = [<int>];
var colors = [<Color>];
var nx: [int] = [];
var ny: [int] = [];
var colors: [Color] = [];

foreach (_ in 0..numCells) {
nx.append(std\random(min: 0, max: width));
Expand Down
45 changes: 40 additions & 5 deletions src/Codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj
// First push on the stack arguments has they are parsed
var needs_reorder = false;
for (components.arguments, 0..) |argument, index| {
const argument_type_def = type_defs[argument.value].?;
var argument_type_def = type_defs[argument.value].?;
const arg_key = if (argument.name) |arg_name|
try self.gc.copyString(lexemes[arg_name])
else
Expand All @@ -1109,6 +1109,9 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj

// Type check the argument
if (def_arg_type) |arg_type| {
self.populateEmptyCollectionType(argument.value, arg_type);
argument_type_def = type_defs[argument.value].?;

if (argument_type_def.def_type == .Placeholder) {
self.reporter.reportPlaceholder(self.ast, argument_type_def.resolved_type.?.Placeholder);
} else if (!arg_type.eql(argument_type_def)) {
Expand Down Expand Up @@ -1513,7 +1516,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.
switch (components.member_kind) {
.Value => {
const value = components.value_or_call_or_enum.Value;
const value_type_def = type_defs[value].?;
var value_type_def = type_defs[value].?;
if (value_type_def.def_type == .Placeholder) {
self.reporter.reportPlaceholder(self.ast, value_type_def.resolved_type.?.Placeholder);
}
Expand Down Expand Up @@ -1571,6 +1574,9 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.
);
}

self.populateEmptyCollectionType(value, field.?.type_def);
value_type_def = type_defs[value].?;

if (!field.?.type_def.eql(value_type_def)) {
self.reporter.reportTypeCheck(
.assignment_value_type,
Expand Down Expand Up @@ -3011,7 +3017,9 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks
self.ast,
type_defs[member.method_or_default_value.?].?.resolved_type.?.Placeholder,
);
} else if (!mkv.value_ptr.*.type_def.eql(type_defs[member.method_or_default_value.?].?)) {
} else if (!mkv.value_ptr.*.type_def.eql(type_defs[member.method_or_default_value.?].?) or
mkv.value_ptr.*.mutable != object_def.fields.get(mkv.key_ptr.*).?.mutable)
{
self.reporter.reportTypeCheck(
.protocol_conforming,
protocol_def.location,
Expand Down Expand Up @@ -3114,7 +3122,9 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks

// Create property default value
if (member.method_or_default_value) |default| {
self.populateEmptyCollectionType(default, property_type);
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)) {
Expand Down Expand Up @@ -3232,11 +3242,12 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error
node_type_def.resolved_type.?.ForeignContainer
.fields.getIndex(property_name);

const value_type_def = type_defs[property.value].?;

if (fields.get(property_name)) |prop| {
try self.OP_COPY(location, 0); // Will be popped by OP_SET_PROPERTY

self.populateEmptyCollectionType(property.value, prop);
const value_type_def = type_defs[property.value].?;

if (value_type_def.def_type == .Placeholder) {
self.reporter.reportPlaceholder(self.ast, value_type_def.resolved_type.?.Placeholder);
} else if (!prop.eql(value_type_def)) {
Expand Down Expand Up @@ -4219,6 +4230,30 @@ fn generateZdef(self: *Self, node: Ast.Node.Index, _: ?*Breaks) Error!?*obj.ObjF
return null;
}

pub fn populateEmptyCollectionType(self: *Self, value: Ast.Node.Index, target_type: *obj.ObjTypeDef) void {
const tags = self.ast.nodes.items(.tag);
const components = self.ast.nodes.items(.components);

// variable: [T] = [<any>] -> variable: [T] = [<T>];
if (target_type.def_type == .List and
tags[value] == .List and
components[value].List.explicit_item_type == null and
components[value].List.items.len == 0)
{
self.ast.nodes.items(.type_def)[value] = target_type;
}

// variable: {K: V} = {<any: any>} -> variable: {K: V} = [<K: V>];
if (target_type.def_type == .Map and
tags[value] == .Map and
components[value].Map.explicit_key_type == null and
components[value].Map.explicit_value_type == null and
components[value].Map.entries.len == 0)
{
self.ast.nodes.items(.type_def)[value] = target_type;
}
}

fn OP_SWAP(self: *Self, location: Ast.TokenIndex, slotA: u24, slotB: u24) !void {
// TODO: both OP_SWAP args could fit in a 32 bit instruction
try self.emitCodeArg(location, .OP_SWAP, slotA);
Expand Down
29 changes: 23 additions & 6 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4619,8 +4619,10 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind
var components = self.ast.nodes.items(.components);
if (can_assign and try self.match(.Equal)) {
components[dot_node].Dot.member_kind = .Value;
const value = try self.expression(false);
components = self.ast.nodes.items(.components);
components[dot_node].Dot.value_or_call_or_enum = .{
.Value = try self.expression(false),
.Value = value,
};
self.ast.nodes.items(.type_def)[dot_node] = property_type;
} else if (try self.match(.LeftParen)) { // Do we call it
Expand Down Expand Up @@ -4672,7 +4674,10 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind
const obj_def = object.resolved_type.?.Object;

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);
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 @@ -4736,6 +4741,7 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind
components[dot_node].Dot.value_or_call_or_enum = .{
.Value = expr,
};

self.ast.nodes.items(.type_def)[dot_node] = property_type;
} else if (try self.match(.LeftParen)) { // If it's a method or placeholder we can call it
// `call` will look to the parent node for the function definition
Expand Down Expand Up @@ -7424,13 +7430,24 @@ fn varDeclaration(
const components = self.ast.nodes.items(.components);
const parsed_type_def = if (parsed_type) |pt| self.ast.nodes.items(.type_def)[pt] else null;

// [T] variable = [] -> [T] variable = [<T>];
if (parsed_type_def != null and parsed_type_def.?.def_type == .List and tags[uvalue] == .List and components[uvalue].List.explicit_item_type == null and components[uvalue].List.items.len == 0) {
// var variable: [T] = [<any>] -> var variable: [T] = [<T>];
if (parsed_type_def != null and
parsed_type_def.?.def_type == .List and
tags[uvalue] == .List and
components[uvalue].List.explicit_item_type == null and
components[uvalue].List.items.len == 0)
{
self.ast.nodes.items(.type_def)[uvalue] = parsed_type_def.?;
}

// {K: V} variable = {} -> {K: V} variable = [<K: V>];
if (parsed_type_def != null and parsed_type_def.?.def_type == .Map and tags[uvalue] == .Map and components[uvalue].Map.explicit_key_type == null and components[uvalue].Map.explicit_value_type == null and components[uvalue].Map.entries.len == 0) {
// var variable: {K: V} = {<any: any>} -> var variable: {K: V} = [<K: V>];
if (parsed_type_def != null and
parsed_type_def.?.def_type == .Map and
tags[uvalue] == .Map and
components[uvalue].Map.explicit_key_type == null and
components[uvalue].Map.explicit_value_type == null and
components[uvalue].Map.entries.len == 0)
{
self.ast.nodes.items(.type_def)[uvalue] = parsed_type_def.?;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/http.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export object Request {
request: ud? = null,
response: Response? = null,
method: Method,
headers: {str: str} = {<str str>},
headers: {str: str} = {},
uri: str = "/",
body: str? = null,
collected: bool = false,
Expand Down Expand Up @@ -261,7 +261,7 @@ export object Request {
// Don't change proeprties order
export object Response {
status: int = 200,
headers: {str: str} = {<str: str>},
headers: {str: str} = {},
body: str? = null,

fun toString() > str {
Expand Down
8 changes: 4 additions & 4 deletions src/lib/serialize.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export object Boxed {
/// When wrapped data is an object, object property values are themselves wrapped in a `Boxed`
fun map() > {str: Boxed}? {
if (this.data as? {str: any} -> dataMap) {
var boxedMap = mut {<str: Boxed>};
var boxedMap: mut {str: Boxed} = mut {};

foreach (key, value in dataMap) {
boxedMap[key] = Boxed{ data = value };
Expand All @@ -51,7 +51,7 @@ export object Boxed {
/// When wrapped data is a list, list elements are themselves warpped in a `Boxed`
fun list() > [Boxed]? {
if (this.data as? [any] -> dataList) {
var boxedList = mut [<Boxed>];
var boxedList: mut [Boxed] = mut [];

foreach (element in dataList) {
boxedList.append(Boxed{ data = element });
Expand Down Expand Up @@ -196,7 +196,7 @@ object JsonParser {
}

mut fun array() > [any] !> JsonParseError, buffer\WriteWhileReadingError {
var array = mut [<any>];
var array: mut [any] = mut [];

while (true) {
this.skipWhitespaces();
Expand All @@ -220,7 +220,7 @@ object JsonParser {
}

mut fun map() > {str: any} !> JsonParseError, buffer\WriteWhileReadingError {
var map = mut {<str: any>};
var map: mut {str: any} = mut {};

while (true) {
this.skipWhitespaces();
Expand Down
8 changes: 4 additions & 4 deletions src/lib/testing.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export fun oncyan(text: str) => color(text, color: Color.oncyan);
export fun onwhite(text: str) => color(text, color: Color.onwhite);

export object Tester {
tests: mut [bool] = mut [<bool>],
asserts: mut [bool] = mut [<bool>],
tests: mut [bool] = mut [],
asserts: mut [bool] = mut [],
elapsed: double = 0.0,
beforeAll: fun (t: Tester) > void?,
beforeEach: fun (t: Tester) > void?,
Expand Down Expand Up @@ -91,8 +91,8 @@ export object Tester {
}

mut fun reset() > void {
this.tests = mut [<bool>];
this.asserts = mut [<bool>];
this.tests = mut [];
this.asserts = mut [];
this.elapsed = 0.0;
}

Expand Down
3 changes: 1 addition & 2 deletions tests/011-list-map-properties.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ object Hey {

test "List and Map as Object properties" {
final hey = Hey{
ages = [<int>, 1, 2, 3],
ages = [1, 2, 3],
names = {
<int: str>,
1: "hello"
},
};
Expand Down
4 changes: 2 additions & 2 deletions tests/019-is.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ test "`is` operator" {
std\assert(MyEnum.one is MyEnum, message: "`is` for an enum instance");
std\assert(myFun is fun (id: int) > int, message: "`is` for a function");

std\assert([<int>, 1,2,3] is [int], message: "`is` on a list");
std\assert({<str: int>, "one": 1} is {str: int}, message: "`is` on a map");
std\assert([1,2,3] is [int], message: "`is` on a list");
std\assert({"one": 1} is {str: int}, message: "`is` on a map");
// TODO: should be `fun (MyObj) > bool`
std\assert(MyObj{}.bound is fun () > bool, message: "`is` on bound method");
}
2 changes: 1 addition & 1 deletion tests/032-debug.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object MyObject {
return MyObject{
name = name,
age = age,
data = Data{ data = [<int>] },
data = Data{ data = [] },
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/037-dead-branches.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test "if" {
}

test "foreach" {
foreach (_ in {<str: str>}) {
foreach (_ in {}) {
std\assert(false, message: "unreachable");
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/041-iterator.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test "finobacci generator" {

object Hello {
fun getRange() > [int] *> int? {
var list = mut [<int>];
var list: mut [int] = mut [];
foreach (i in 0..10) {
_ = yield i;

Expand Down
2 changes: 1 addition & 1 deletion tests/042-anonymous-objects.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object A {

fun free() > void {
foreach (x, y in this.board) {
var board = mut [<obj{ x: int }>];
var board: mut [obj{ x: int }] = mut [];

board.append(.{ x = x });
}
Expand Down
Loading

0 comments on commit a6a039c

Please sign in to comment.