Merge pull request #126 from birth-software/polymorphic-type-sketch

First polymorphic type implementation
This commit is contained in:
David 2024-04-05 09:33:11 -06:00 committed by GitHub
commit 2343a1ff4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 263 additions and 41 deletions

View File

@ -3222,6 +3222,7 @@ pub const Type = union(enum) {
void,
noreturn,
type,
polymorphic: *Debug.Declaration.Global,
@"struct": Struct.Index,
function: Function.Prototype.Index,
integer: Type.Integer,
@ -3837,10 +3838,6 @@ pub const Struct = struct {
pub const Kind = union(enum) {
@"struct": Struct.Descriptor,
@"union": struct {
scope: Debug.Scope.Global,
fields: UnpinnedArray(Struct.Field.Index) = .{},
},
error_union: struct {
@"error": Type.Index,
type: Type.Index,
@ -3864,11 +3861,21 @@ pub const Struct = struct {
};
pub const Options = struct {
sliceable: ?Sliceable = null,
polymorphism_kind: PolymorphismKind = .none,
pub const Id = enum {
sliceable,
};
};
pub const PolymorphismKind = union(enum) {
none,
declaration: []const Type.Index,
implementation: struct {
types: []const Type.Index,
original: Type.Index,
},
};
pub const Sliceable = struct {
pointer: u32,
length: u32,
@ -4509,7 +4516,7 @@ pub const Builder = struct {
// TODO: depends? .right is not always the right choice
const v = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, argument_node_index, .right);
const cast_id: Instruction.Cast.Id = switch (type_expect) {
switch (type_expect) {
.type => |type_index| {
const cast_id = try builder.resolveCast(unit, context, type_index, v);
switch (cast_id) {
@ -4534,9 +4541,7 @@ pub const Builder = struct {
}
},
else => |t| @panic(@tagName(t)),
};
_ = cast_id; // autofix
}
},
.size => {
assert(argument_node_list.len == 1);
@ -6512,12 +6517,127 @@ pub const Builder = struct {
break :blk unit.all_errors;
}
},
// This is a data structures with parameters
.call => b: {
const parameterized_type_index = try builder.resolveType(unit, context, node.left);
const parameter_nodes = unit.getNodeList(node.right);
var parameter_types = UnpinnedArray(Type.Index){};
for (parameter_nodes) |parameter_node_index| {
const parameter_node = unit.getNode(parameter_node_index);
switch (parameter_node.id) {
.unsigned_integer_type => try parameter_types.append(context.my_allocator, try builder.resolveType(unit, context, parameter_node_index)),
else => |t| @panic(@tagName(t)),
}
}
const instantiated_type_index = try builder.instantiate_polymorphic_type(unit, context, parameterized_type_index, parameter_types.slice(), parameterized_type_index);
break :b instantiated_type_index;
},
else => |t| @panic(@tagName(t)),
};
return result;
}
fn instantiate_polymorphic_type(builder: *Builder, unit: *Unit, context: *const Context, type_index: Type.Index, parameter_types: []const Type.Index, original_type_index: Type.Index) !Type.Index {
const parameterized_type = unit.types.get(type_index);
const original_type = unit.types.get(original_type_index);
switch (parameterized_type.*) {
.@"struct" => |struct_index| switch (unit.structs.get(struct_index).kind) {
.@"struct" => |*struct_type| {
switch (struct_type.options.polymorphism_kind) {
.declaration => |declaration_types| {
if (declaration_types.len != parameter_types.len) @panic("Argument count mismatch");
// var s = struct_type.*;
var fields = try UnpinnedArray(Struct.Field.Index).initialize_with_capacity(context.my_allocator, struct_type.fields.length);
var is_polymorphic = false;
for (struct_type.fields.slice()) |field_index| {
const field = unit.struct_fields.get(field_index);
const new_type_index = try builder.instantiate_polymorphic_type(unit, context, field.type, parameter_types, original_type_index);
is_polymorphic = is_polymorphic or new_type_index == field.type;
if (field.type != new_type_index) {
var f = field.*;
f.type = new_type_index;
const new_field_index = try unit.struct_fields.append(context.my_allocator, f);
fields.append_with_capacity(new_field_index);
} else {
fields.append_with_capacity(field_index);
}
}
if (is_polymorphic) {
var s = struct_type.*;
s.fields = fields;
s.options.polymorphism_kind = .{
.implementation = .{
.types = parameter_types,
.original = type_index,
},
};
const si = try unit.structs.append(context.my_allocator, .{
.kind = .{
.@"struct" = s,
},
});
const sti = try unit.types.append(context.my_allocator, .{
.@"struct" = si,
});
return sti;
} else {
unreachable;
}
},
else => |t| @panic(@tagName(t)),
}
},
else => |t| @panic(@tagName(t)),
},
.pointer => |pointer| {
const new_type_index = try builder.instantiate_polymorphic_type(unit, context, pointer.type, parameter_types, original_type_index);
if (new_type_index != pointer.type) {
var p = pointer;
p.type = new_type_index;
return try unit.getPointerType(context, p);
} else {
unreachable;
}
},
.polymorphic => |polymorphic_declaration| {
assert(polymorphic_declaration.initial_value.type == type_index);
const name_hash = polymorphic_declaration.declaration.name;
switch (original_type.*) {
.@"struct" => |struct_index| switch (unit.structs.get(struct_index).kind) {
.@"struct" => |*struct_type| {
switch (struct_type.options.polymorphism_kind) {
.declaration => |declaration_types| {
for (declaration_types, parameter_types) |declaration_type_index, parameter_type_index| {
if (declaration_type_index == type_index) {
return parameter_type_index;
}
}
unreachable;
},
else => |t| @panic(@tagName(t)),
}
unreachable;
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
}
_ = name_hash; // autofix
unreachable;
},
.integer => return type_index,
else => |t| @panic(@tagName(t)),
}
}
fn get_builtin_declaration(builder: *Builder, unit: *Unit, context: *const Context, name: []const u8) !*Debug.Declaration.Global {
const std_file_index = try builder.resolveImportStringLiteral(unit, context, Type.Expect{ .type = .type }, "std");
const std_file = unit.files.get(std_file_index);
@ -7502,11 +7622,39 @@ pub const Builder = struct {
.@"struct" => b: {
assert(container_node.id == .struct_type);
var struct_options = Struct.Options{};
const struct_index = try unit.structs.append(context.my_allocator, .{
.kind = .{
.@"struct" = .{
.scope = .{
.scope = .{
.kind = switch (builder.current_scope.kind) {
.file => .file_container,
else => .container,
},
.line = token_debug_info.line,
.column = token_debug_info.column,
.level = builder.current_scope.level + 1,
.local = false,
.file = builder.current_file,
},
},
.options = .{},
},
},
});
const type_index = try unit.types.append(context.my_allocator, .{
.@"struct" = struct_index,
});
const struct_type = unit.structs.get(struct_index);
const struct_options = & struct_type.kind.@"struct".options;
if (container_node.right != .null) {
const struct_option_nodes = unit.getNodeList(container_node.right);
var struct_options_value = false;
var parameter_types = UnpinnedArray(Type.Index){};
for (struct_option_nodes) |struct_option_node_index| {
const struct_option_node = unit.getNode(struct_option_node_index);
switch (struct_option_node.id) {
@ -7538,37 +7686,48 @@ pub const Builder = struct {
}
}
},
.comptime_expression => {
assert(struct_option_node.left != .null);
assert(struct_option_node.right == .null);
const declaration_token_debug_info = builder.getTokenDebugInfo(unit, struct_option_node.token);
const left = unit.getNode(struct_option_node.left);
assert(left.id == .identifier);
const identifier = unit.getExpectedTokenBytes(left.token, .identifier);
const hash = try unit.processIdentifier(context, identifier);
const polymorphic_type_index = try unit.types.append(context.my_allocator, .{
.polymorphic = undefined,
});
const global_declaration_index = try unit.global_declarations.append(context.my_allocator, .{
.declaration = .{
.scope = &struct_type.kind.@"struct".scope.scope,
.name = hash,
.type = .type,
.line = declaration_token_debug_info.line,
.column = declaration_token_debug_info.column,
.mutability = .@"const",
.kind = .global,
},
.initial_value = .{
.type = polymorphic_type_index,
},
.type_node_index = .null,
.attributes = .{},
});
const global_declaration = unit.global_declarations.get(global_declaration_index);
try struct_type.kind.@"struct".scope.scope.declarations.put_no_clobber(context.my_allocator, hash, &global_declaration.declaration);
const polymorphic_type = unit.types.get(polymorphic_type_index);
polymorphic_type.polymorphic = global_declaration;
try parameter_types.append(context.my_allocator, polymorphic_type_index);
},
else => |t| @panic(@tagName(t)),
}
}
struct_type.kind.@"struct".options.polymorphism_kind = .{
.declaration = parameter_types.slice(),
};
}
const struct_index = try unit.structs.append(context.my_allocator, .{
.kind = .{
.@"struct" = .{
.scope = .{
.scope = .{
.kind = switch (builder.current_scope.kind) {
.file => .file_container,
else => .container,
},
.line = token_debug_info.line,
.column = token_debug_info.column,
.level = builder.current_scope.level + 1,
.local = false,
.file = builder.current_file,
},
},
.options = struct_options,
},
},
});
const struct_type = unit.structs.get(struct_index);
const type_index = try unit.types.append(context.my_allocator, .{
.@"struct" = struct_index,
});
// Save file type
switch (builder.current_scope.kind) {
.file => {
@ -8681,6 +8840,26 @@ pub const Builder = struct {
else => |t| @panic(@tagName(t)),
}
},
.cast => {
assert(argument_node_list.len == 1);
switch (type_expect) {
.type => |type_index| {
const value = try builder.resolveComptimeValue(unit, context, Type.Expect.none, .{}, argument_node_list[0], null, .right);
const ty = unit.types.get(type_index);
switch (ty.*) {
.pointer => |_| switch (value) {
.@"comptime_int" => |ct_int| switch (ct_int.value) {
0 => return .null_pointer,
else => unreachable,
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
}
},
else => |t| @panic(@tagName(t)),
}
},
else => |t| @panic(@tagName(t)),
}
},
@ -10988,6 +11167,15 @@ pub const Builder = struct {
else => unreachable,
}, unreachable),
},
.empty_container_literal_guess => {
assert(node.left != .null);
assert(node.right != .null);
const container_type = try builder.resolveType(unit, context, node.left);
const node_list = unit.getNodeList(node.right);
assert(node_list.len == 0);
const result = try builder.resolveContainerLiteral(unit, context, node_list, container_type);
return result;
},
else => |t| @panic(@tagName(t)),
};
@ -15832,6 +16020,13 @@ pub const Unit = struct {
return type_index;
}
}
fn getPolymorphicTypes(unit: *Unit, type_index: Type.Index) []const Type.Index {
const ty = unit.types.get(type_index);
return switch (ty.*) {
else => |t| @panic(@tagName(t)),
};
}
};
pub const FixedKeyword = enum {
@ -15854,7 +16049,6 @@ pub const FixedKeyword = enum {
@"else",
@"struct",
@"enum",
@"union",
null,
@"align",
@"for",

View File

@ -196,6 +196,7 @@ pub const Node = struct {
payload,
catch_payload,
bitfield_type,
comptime_expression,
};
};
@ -1210,6 +1211,7 @@ const Analyzer = struct {
}
fn primaryExpression(analyzer: *Analyzer) !Node.Index {
const token = analyzer.token_i;
const result = switch (analyzer.peekToken()) {
.identifier => switch (analyzer.peekTokenAhead(1)) {
// TODO: tags
@ -1244,7 +1246,6 @@ const Analyzer = struct {
.fixed_keyword_return => try analyzer.addNode(.{
.id = .@"return",
.token = blk: {
const token = analyzer.token_i;
analyzer.consumeToken();
break :blk token;
},
@ -1256,6 +1257,16 @@ const Analyzer = struct {
.operator_left_brace => try analyzer.block(),
.fixed_keyword_if => try analyzer.ifExpression(),
.fixed_keyword_bitfield => try analyzer.processContainerType(.fixed_keyword_bitfield),
.operator_dollar => blk: {
analyzer.consumeToken();
const t = try analyzer.typeExpression();
break :blk try analyzer.addNode(.{
.id = .comptime_expression,
.token = token,
.left = t,
.right = .null,
});
},
else => |id| @panic(@tagName(id)),
};
@ -1653,7 +1664,7 @@ const Analyzer = struct {
const initialization: InitializationType = current_initialization orelse switch (type_node) {
.null => .empty_literal,
else => switch (analyzer.nodes.get(type_node).id) {
.identifier => .empty_container_literal_guess,
.identifier, .call => .empty_container_literal_guess,
.array_type => .empty_array_literal,
else => |t| @panic(@tagName(t)),
},

View File

@ -0,0 +1 @@
const PinnedArray = #import("data_structures/pinned_array.nat").PinnedArray;

View File

@ -0,0 +1,5 @@
const PinnedArray = struct(.{ .sliceable = true }, $T) {
pointer: [&]T = #cast(0),
length: u32 = 0,
capacity: u32 = 0,
};

View File

@ -5,17 +5,22 @@ comptime {
test {
_ = build;
_ = builtin;
_ = data_structures;
_ = os;
_ = start;
}
const build = #import("build.nat");
const builtin = #import("builtin.nat");
const os = #import("os.nat");
const c = #import("c.nat");
const start = #import("start.nat");
const testing = #import("testing.nat");
const data_structures = #import("data_structures.nat");
const c = #import("c.nat");
const os = #import("os.nat");
const start = #import("start.nat");
const assert = fn(ok: bool) void {
if (!ok) {
unreachable;

View File

@ -0,0 +1,6 @@
const std = #import("std");
const PinnedArray = std.data_structures.PinnedArray;
const main = fn () *!void {
var pinned_array = PinnedArray(u32){};
}