vtables, protocol

This commit is contained in:
agra
2026-02-24 06:20:38 +02:00
parent 0cc7b69441
commit 170e236764
16 changed files with 3032 additions and 294 deletions

View File

@@ -64,6 +64,7 @@ pub const Node = struct {
break_expr: void,
continue_expr: void,
undef_literal: void,
inferred_type: void,
builtin_expr: void,
foreign_expr: ForeignExpr,
library_decl: LibraryDecl,
@@ -73,6 +74,8 @@ pub const Node = struct {
tuple_literal: TupleLiteral,
ufcs_alias: UfcsAlias,
c_import_decl: CImportDecl,
protocol_decl: ProtocolDecl,
impl_block: ImplBlock,
pub fn declName(self: Data) ?[]const u8 {
return switch (self) {
@@ -85,6 +88,7 @@ pub const Node = struct {
.namespace_decl => |d| d.name,
.ufcs_alias => |d| d.name,
.c_import_decl => |d| d.name,
.protocol_decl => |d| d.name,
else => null,
};
}
@@ -274,6 +278,7 @@ pub const UnionDecl = struct {
pub const StructTypeParam = struct {
name: []const u8, // e.g. "N" or "T" (without $)
constraint: *Node, // type_expr: "u32" for value param, "Type" for type param
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
};
pub const UsingEntry = struct {
@@ -288,6 +293,7 @@ pub const StructDecl = struct {
field_defaults: []const ?*Node, // default value per field, null if none
type_params: []const StructTypeParam = &.{},
using_entries: []const UsingEntry = &.{},
methods: []const *Node = &.{}, // fn_decl nodes for struct methods
};
pub const StructFieldInit = struct {
@@ -311,6 +317,7 @@ pub const Lambda = struct {
pub const TypeExpr = struct {
name: []const u8,
is_generic: bool = false,
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
};
pub const DeferStmt = struct {
@@ -465,3 +472,24 @@ pub const CImportDecl = struct {
name: ?[]const u8 = null,
bitcode_paths: []const []const u8 = &.{}, // populated during import resolution
};
pub const ProtocolMethodDecl = struct {
name: []const u8,
params: []const *Node, // type_expr nodes for parameter types (excluding implicit self)
param_names: []const []const u8, // parameter names (excluding implicit self)
return_type: ?*Node, // null = void return
default_body: ?*Node, // null = required method, non-null = default implementation
};
pub const ProtocolDecl = struct {
name: []const u8,
methods: []const ProtocolMethodDecl,
is_inline: bool = false, // #inline — embedded fn ptrs instead of vtable pointer
};
pub const ImplBlock = struct {
protocol_name: []const u8,
target_type: []const u8,
target_type_params: []const StructTypeParam = &.{}, // for `impl P for List($T)`
methods: []const *Node, // fn_decl nodes
};

File diff suppressed because it is too large Load Diff

View File

@@ -289,7 +289,7 @@ pub const UnionFieldType = enum { int, float, bool_k, pointer, string };
pub const ValueKind = enum { int, float, f32_k, bool_k, string };
pub const BuiltinId = enum { print, out, sqrt, size_of, cast, malloc, free, memcpy, memset, type_of };
pub const BuiltinId = enum { print, out, sqrt, size_of, cast, malloc, free, memcpy, memset, type_of, alloc, dealloc };
/// A compiled function or expression — a flat sequence of instructions.
pub const Chunk = struct {
@@ -1864,6 +1864,56 @@ pub const VM = struct {
}
}
// Resolve UFCS aliases first (comptime builtins like allocator_alloc need priority)
for (self.root_decls) |decl| {
switch (decl.data) {
.ufcs_alias => |ua| {
if (std.mem.eql(u8, ua.name, name)) {
// Check if target is a builtin
if (std.meta.stringToEnum(BuiltinId, ua.target)) |id|
return self.callBuiltin(id, arg_count);
return self.callFunction(ua.target, arg_count);
}
},
.namespace_decl => |ns| {
for (ns.decls) |d| {
if (d.data == .ufcs_alias) {
const ua = d.data.ufcs_alias;
if (std.mem.eql(u8, ua.name, name)) {
if (std.meta.stringToEnum(BuiltinId, ua.target)) |id|
return self.callBuiltin(id, arg_count);
return self.callFunction(ua.target, arg_count);
}
}
}
},
else => {},
}
}
// Search struct methods (after UFCS aliases, so builtins take priority)
for (self.root_decls) |decl| {
switch (decl.data) {
.struct_decl => |sd| {
for (sd.methods) |m| {
if (m.data == .fn_decl and std.mem.eql(u8, m.data.fn_decl.name, name))
return self.compileFunctionAndInvoke(name, m.data.fn_decl, arg_count);
}
},
.namespace_decl => |ns| {
for (ns.decls) |d| {
if (d.data == .struct_decl) {
for (d.data.struct_decl.methods) |m| {
if (m.data == .fn_decl and std.mem.eql(u8, m.data.fn_decl.name, name))
return self.compileFunctionAndInvoke(name, m.data.fn_decl, arg_count);
}
}
}
},
else => {},
}
}
return error.UndefinedFunction;
}
@@ -2038,6 +2088,31 @@ pub const VM = struct {
}
try self.push(.{ .void_val = {} });
},
.alloc => {
// alloc(size) or alloc(allocator, size) — at comptime, equivalent to malloc
if (arg_count >= 2) {
const size_val = try self.pop();
_ = try self.pop(); // discard allocator struct
const size: usize = if (size_val.asInt()) |v| @intCast(@max(0, v)) else 0;
const buf = try self.allocator.alloc(u8, size);
@memset(buf, 0);
try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } });
} else if (arg_count == 1) {
const val = try self.pop();
const size: usize = if (val.asInt()) |v| @intCast(@max(0, v)) else 0;
const buf = try self.allocator.alloc(u8, size);
@memset(buf, 0);
try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } });
} else {
try self.push(.{ .byte_ptr_val = .{ .data = &.{}, .offset = 0 } });
}
},
.dealloc => {
// dealloc(ptr) — at comptime, no-op
var i: u8 = 0;
while (i < arg_count) : (i += 1) _ = try self.pop();
try self.push(.{ .void_val = {} });
},
.type_of => {
// type_of(val) — return the type tag (matching ANY_TAG_* constants)
if (arg_count >= 1) {

View File

@@ -76,6 +76,7 @@ pub const Lexer = struct {
.{ "#source", Tag.hash_source },
.{ "#define", Tag.hash_define },
.{ "#flags", Tag.hash_flags },
.{ "#inline", Tag.hash_inline },
};
inline for (directives) |d| {
const keyword = d[0];

View File

@@ -399,6 +399,7 @@ pub const Server = struct {
.constant => @intFromEnum(lsp.SymbolKindLsp.Constant),
.enum_type => @intFromEnum(lsp.SymbolKindLsp.Enum),
.struct_type => @intFromEnum(lsp.SymbolKindLsp.Struct),
.protocol_type => @intFromEnum(lsp.SymbolKindLsp.Interface),
.type_alias => @intFromEnum(lsp.SymbolKindLsp.Class),
.param => @intFromEnum(lsp.SymbolKindLsp.Variable),
.namespace => @intFromEnum(lsp.SymbolKindLsp.Namespace),
@@ -455,6 +456,7 @@ pub const Server = struct {
.constant => @intFromEnum(lsp.CompletionItemKind.Constant),
.enum_type => @intFromEnum(lsp.CompletionItemKind.Enum),
.struct_type => @intFromEnum(lsp.CompletionItemKind.Struct),
.protocol_type => @intFromEnum(lsp.CompletionItemKind.Interface),
.type_alias => @intFromEnum(lsp.CompletionItemKind.Class),
.param => @intFromEnum(lsp.CompletionItemKind.Variable),
.namespace => @intFromEnum(lsp.CompletionItemKind.Module),
@@ -790,11 +792,13 @@ pub const Server = struct {
for (fd.params, 0..) |param, pi| {
if (pi > 0) try detail_buf.appendSlice(allocator, ", ");
try detail_buf.appendSlice(allocator, param.name);
try detail_buf.appendSlice(allocator, ": ");
if (param.type_expr.data == .type_expr) {
try detail_buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
} else {
try detail_buf.appendSlice(allocator, "?");
if (param.type_expr.data != .inferred_type) {
try detail_buf.appendSlice(allocator, ": ");
if (param.type_expr.data == .type_expr) {
try detail_buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
} else {
try detail_buf.appendSlice(allocator, "?");
}
}
}
try detail_buf.append(allocator, ')');
@@ -838,6 +842,12 @@ pub const Server = struct {
.kind = @intFromEnum(lsp.CompletionItemKind.Struct),
});
},
.protocol_decl => |pd| {
try items.append(allocator, .{
.label = pd.name,
.kind = @intFromEnum(lsp.CompletionItemKind.Interface),
});
},
.var_decl => |vd| {
try items.append(allocator, .{
.label = vd.name,
@@ -892,6 +902,43 @@ pub const Server = struct {
}
}
}
} else if (sym.kind == .protocol_type) {
const lookup_root = if (sym.origin) |origin_path|
if (self.documents.get(origin_path)) |od| od.root orelse root else root
else
root;
if (sx.sema.findNodeAtOffset(lookup_root, sym.def_span.start)) |node| {
if (node.data == .protocol_decl) {
const pd = node.data.protocol_decl;
for (pd.methods) |method| {
// Build detail string: (params) -> ret
var detail_buf = std.ArrayList(u8).empty;
try detail_buf.append(self.allocator, '(');
for (method.param_names, 0..) |pname, pi| {
if (pi > 0) try detail_buf.appendSlice(self.allocator, ", ");
try detail_buf.appendSlice(self.allocator, pname);
if (pi < method.params.len) {
try detail_buf.appendSlice(self.allocator, ": ");
if (method.params[pi].data == .type_expr) {
try detail_buf.appendSlice(self.allocator, method.params[pi].data.type_expr.name);
}
}
}
try detail_buf.append(self.allocator, ')');
if (method.return_type) |rt| {
try detail_buf.appendSlice(self.allocator, " -> ");
if (rt.data == .type_expr) {
try detail_buf.appendSlice(self.allocator, rt.data.type_expr.name);
}
}
try items.append(self.allocator, .{
.label = method.name,
.kind = @intFromEnum(lsp.CompletionItemKind.Method),
.detail = detail_buf.items,
});
}
}
}
}
break;
}
@@ -956,11 +1003,13 @@ pub const Server = struct {
if (pi > 0) try label_buf.appendSlice(self.allocator, ", ");
const param_start = label_buf.items.len;
try label_buf.appendSlice(self.allocator, param.name);
try label_buf.appendSlice(self.allocator, ": ");
if (param.type_expr.data == .type_expr) {
try label_buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name);
} else {
try label_buf.appendSlice(self.allocator, "?");
if (param.type_expr.data != .inferred_type) {
try label_buf.appendSlice(self.allocator, ": ");
if (param.type_expr.data == .type_expr) {
try label_buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name);
} else {
try label_buf.appendSlice(self.allocator, "?");
}
}
const param_label = try self.allocator.dupe(u8, label_buf.items[param_start..]);
try param_labels.append(self.allocator, param_label);
@@ -1361,6 +1410,9 @@ pub const Server = struct {
.kw_push,
.kw_ufcs,
.kw_in,
.kw_closure,
.kw_protocol,
.kw_impl,
.hash_run,
.hash_import,
.hash_insert,
@@ -1372,9 +1424,10 @@ pub const Server = struct {
.hash_source,
.hash_define,
.hash_flags,
.hash_inline,
=> ST.keyword,
.kw_f32, .kw_f64, .kw_Type => ST.type_,
.kw_f32, .kw_f64, .kw_Type, .kw_Self => ST.type_,
.int_literal, .float_literal => ST.number,
.string_literal, .raw_string_literal => null,
@@ -1480,6 +1533,7 @@ pub const Server = struct {
.param => ST.parameter,
.enum_type => ST.enum_,
.struct_type => ST.struct_,
.protocol_type => ST.interface,
.type_alias => ST.type_,
.namespace => ST.namespace,
};
@@ -1817,8 +1871,10 @@ pub const Server = struct {
for (params, 0..) |param, pi| {
if (pi > 0) try buf.appendSlice(self.allocator, ", ");
try buf.appendSlice(self.allocator, param.name);
try buf.appendSlice(self.allocator, ": ");
if (param.type_expr.data == .type_expr) {
if (param.type_expr.data == .inferred_type) {
// Inferred type — show name only
} else if (param.type_expr.data == .type_expr) {
try buf.appendSlice(self.allocator, ": ");
try buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name);
} else {
try buf.appendSlice(self.allocator, "?");
@@ -2305,11 +2361,13 @@ pub const Server = struct {
for (fd.params, 0..) |param, pi| {
if (pi > 0) try buf.appendSlice(allocator, ", ");
try buf.appendSlice(allocator, param.name);
try buf.appendSlice(allocator, ": ");
if (param.type_expr.data == .type_expr) {
try buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
} else {
try buf.appendSlice(allocator, "?");
if (param.type_expr.data != .inferred_type) {
try buf.appendSlice(allocator, ": ");
if (param.type_expr.data == .type_expr) {
try buf.appendSlice(allocator, param.type_expr.data.type_expr.name);
} else {
try buf.appendSlice(allocator, "?");
}
}
}
try buf.append(allocator, ')');
@@ -2407,6 +2465,44 @@ pub const Server = struct {
}
try buf.appendSlice(allocator, " }");
},
.protocol_decl => |pd| {
try buf.appendSlice(allocator, pd.name);
try buf.appendSlice(allocator, " :: protocol");
if (pd.is_inline) try buf.appendSlice(allocator, " #inline");
try buf.appendSlice(allocator, " { ");
for (pd.methods, 0..) |method, mi| {
if (mi > 0) try buf.appendSlice(allocator, " ");
try buf.appendSlice(allocator, method.name);
try buf.appendSlice(allocator, " :: (");
for (method.param_names, 0..) |pname, pi| {
if (pi > 0) try buf.appendSlice(allocator, ", ");
try buf.appendSlice(allocator, pname);
if (pi < method.params.len) {
try buf.appendSlice(allocator, ": ");
if (method.params[pi].data == .type_expr) {
try buf.appendSlice(allocator, method.params[pi].data.type_expr.name);
} else {
try buf.appendSlice(allocator, "?");
}
}
}
try buf.append(allocator, ')');
if (method.return_type) |rt| {
try buf.appendSlice(allocator, " -> ");
if (rt.data == .type_expr) {
try buf.appendSlice(allocator, rt.data.type_expr.name);
}
}
try buf.appendSlice(allocator, ";");
}
try buf.appendSlice(allocator, " }");
},
.impl_block => |ib| {
try buf.appendSlice(allocator, "impl ");
try buf.appendSlice(allocator, ib.protocol_name);
try buf.appendSlice(allocator, " for ");
try buf.appendSlice(allocator, ib.target_type);
},
.const_decl => |cd| {
try buf.appendSlice(allocator, cd.name);
try buf.appendSlice(allocator, " :: ");
@@ -2507,6 +2603,10 @@ pub const Server = struct {
try buf.appendSlice(allocator, sym.name);
try buf.appendSlice(allocator, " :: struct { ... }");
},
.protocol_type => {
try buf.appendSlice(allocator, sym.name);
try buf.appendSlice(allocator, " :: protocol { ... }");
},
.type_alias => {
try buf.appendSlice(allocator, sym.name);
try buf.appendSlice(allocator, " :: (type)");

View File

@@ -106,7 +106,7 @@ pub fn initializeResultJson(allocator: std.mem.Allocator) ![]const u8 {
"\"completionProvider\":{{\"triggerCharacters\":[\".\"]}}," ++
"\"signatureHelpProvider\":{{\"triggerCharacters\":[\"(\",\",\"]}}," ++
"\"semanticTokensProvider\":{{\"legend\":{{" ++
"\"tokenTypes\":[\"namespace\",\"type\",\"enum\",\"struct\",\"parameter\",\"variable\",\"enumMember\",\"function\",\"keyword\",\"number\",\"string\",\"operator\"]," ++
"\"tokenTypes\":[\"namespace\",\"type\",\"enum\",\"struct\",\"parameter\",\"variable\",\"enumMember\",\"function\",\"keyword\",\"number\",\"string\",\"operator\",\"interface\"]," ++
"\"tokenModifiers\":[\"declaration\",\"readonly\"]" ++
"}},\"full\":true}}," ++
"\"inlayHintProvider\":true}}}}",
@@ -321,6 +321,7 @@ pub const SemanticTokenType = struct {
pub const number: u32 = 9;
pub const string_: u32 = 10;
pub const operator_: u32 = 11;
pub const interface: u32 = 12;
};
/// Build a SemanticTokens JSON response.

View File

@@ -16,6 +16,8 @@ pub const Parser = struct {
err_offset: ?u32 = null,
prev_end: u32 = 0,
diagnostics: ?*errors.DiagnosticList = null,
/// Type param names from enclosing generic struct (set while parsing methods)
struct_type_params: []const []const u8 = &.{},
pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser {
var lexer = Lexer.init(source);
@@ -75,8 +77,13 @@ pub const Parser = struct {
return try self.createNode(start, .{ .comptime_expr = .{ .expr = expr } });
}
// impl Protocol for Type { methods }
if (self.current.tag == .kw_impl) {
return self.parseImplBlock(start);
}
// All top-level declarations start with an identifier
if (self.current.tag != .identifier) {
if (self.current.tag != .identifier and self.current.tag != .kw_Self) {
return self.fail("expected identifier at top level");
}
const name = self.tokenSlice(self.current);
@@ -170,6 +177,11 @@ pub const Parser = struct {
return self.parseStructDecl(name, start_pos);
}
// Protocol declaration
if (self.current.tag == .kw_protocol) {
return self.parseProtocolDecl(name, start_pos);
}
// C-style union declaration
if (self.current.tag == .kw_union) {
return self.parseUnionDecl(name, start_pos);
@@ -378,7 +390,7 @@ pub const Parser = struct {
return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } });
}
// Generic type parameter introduction: $T
// Generic type parameter introduction: $T or $T/Protocol1/Protocol2
if (self.current.tag == .dollar) {
self.advance();
if (self.current.tag != .identifier) {
@@ -386,7 +398,18 @@ pub const Parser = struct {
}
const name = self.tokenSlice(self.current);
self.advance();
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true } });
// Parse optional protocol constraints: $T/Eq/Hashable
var constraints = std.ArrayList([]const u8).empty;
while (self.current.tag == .slash) {
self.advance(); // skip '/'
if (self.current.tag != .identifier) {
return self.fail("expected protocol name after '/'");
}
try constraints.append(self.allocator, self.tokenSlice(self.current));
self.advance();
}
const pc = try constraints.toOwnedSlice(self.allocator);
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true, .protocol_constraints = pc } });
}
// Function type: (ParamTypes) -> ReturnType
// Tuple type: (T1, T2) or (T1) — no '->' after ')'
@@ -525,7 +548,12 @@ pub const Parser = struct {
} });
}
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
// Mark as generic if name matches an enclosing struct's type param
var is_struct_generic = false;
for (self.struct_type_params) |tp| {
if (std.mem.eql(u8, tp, name)) { is_struct_generic = true; break; }
}
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = is_struct_generic } });
}
// Inline struct type in type position: struct { ... }
if (self.current.tag == .kw_struct) {
@@ -682,17 +710,38 @@ pub const Parser = struct {
self.advance();
try self.expect(.colon);
const constraint = try self.parseTypeExpr();
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint });
// Parse optional protocol constraints: $T: Type/Eq/Hashable
var pc_list = std.ArrayList([]const u8).empty;
if (constraint.data == .type_expr and std.mem.eql(u8, constraint.data.type_expr.name, "Type")) {
while (self.current.tag == .slash) {
self.advance(); // skip '/'
if (self.current.tag != .identifier) {
return self.fail("expected protocol name after '/'");
}
try pc_list.append(self.allocator, self.tokenSlice(self.current));
self.advance();
}
}
const pc = try pc_list.toOwnedSlice(self.allocator);
try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint, .protocol_constraints = pc });
}
try self.expect(.r_paren);
}
try self.expect(.l_brace);
// Set struct type params context so method params can reference T without $
var tp_names = std.ArrayList([]const u8).empty;
for (type_params.items) |tp| try tp_names.append(self.allocator, tp.name);
const saved_struct_type_params = self.struct_type_params;
self.struct_type_params = tp_names.items;
defer self.struct_type_params = saved_struct_type_params;
var field_names = std.ArrayList([]const u8).empty;
var field_types = std.ArrayList(*Node).empty;
var field_defaults = std.ArrayList(?*Node).empty;
var using_entries = std.ArrayList(ast.UsingEntry).empty;
var methods = std.ArrayList(*Node).empty;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
// Check for #using directive
@@ -711,6 +760,20 @@ pub const Parser = struct {
continue;
}
// Method declaration: name :: (params) -> type { body }
if (self.current.tag == .identifier and self.peekNext() == .colon_colon) {
const method_start = self.current.loc.start;
const method_name = self.tokenSlice(self.current);
self.advance(); // skip name
self.advance(); // skip ::
if (self.current.tag == .l_paren and self.isFunctionDef()) {
try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start));
} else {
return self.fail("only function declarations are allowed inside struct bodies");
}
continue;
}
// Parse field group: name1, name2, ...: type (= default)?;
var group_names = std.ArrayList([]const u8).empty;
@@ -764,6 +827,166 @@ pub const Parser = struct {
.field_defaults = try field_defaults.toOwnedSlice(self.allocator),
.type_params = try type_params.toOwnedSlice(self.allocator),
.using_entries = try using_entries.toOwnedSlice(self.allocator),
.methods = try methods.toOwnedSlice(self.allocator),
} });
}
fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
self.advance(); // skip 'protocol'
// Check for #inline
var is_inline = false;
if (self.current.tag == .hash_inline) {
is_inline = true;
self.advance();
}
try self.expect(.l_brace);
var methods = std.ArrayList(ast.ProtocolMethodDecl).empty;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
// Method: name :: (params) -> type; or name :: (params) -> type { body }
if (self.current.tag != .identifier) {
return self.fail("expected method name in protocol body");
}
const method_name = self.tokenSlice(self.current);
self.advance();
try self.expect(.colon_colon);
try self.expect(.l_paren);
var param_types = std.ArrayList(*Node).empty;
var param_names = std.ArrayList([]const u8).empty;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (param_types.items.len > 0) {
try self.expect(.comma);
}
// Parse: name: type
if (self.current.tag != .identifier and self.current.tag != .kw_Self) {
return self.fail("expected parameter name in protocol method");
}
const pname = self.tokenSlice(self.current);
self.advance();
try self.expect(.colon);
const ptype = try self.parseTypeExpr();
try param_names.append(self.allocator, pname);
try param_types.append(self.allocator, ptype);
}
try self.expect(.r_paren);
// Optional return type
var return_type: ?*Node = null;
if (self.current.tag == .arrow) {
self.advance();
return_type = try self.parseTypeExpr();
}
// Optional body (default method) or semicolon
var default_body: ?*Node = null;
if (self.current.tag == .l_brace) {
default_body = try self.parseBlock();
} else {
if (self.current.tag == .semicolon) self.advance();
}
try methods.append(self.allocator, .{
.name = method_name,
.params = try param_types.toOwnedSlice(self.allocator),
.param_names = try param_names.toOwnedSlice(self.allocator),
.return_type = return_type,
.default_body = default_body,
});
}
try self.expect(.r_brace);
return try self.createNode(start_pos, .{ .protocol_decl = .{
.name = name,
.methods = try methods.toOwnedSlice(self.allocator),
.is_inline = is_inline,
} });
}
fn parseImplBlock(self: *Parser, start_pos: u32) anyerror!*Node {
self.advance(); // skip 'impl'
// Protocol name
if (self.current.tag != .identifier) {
return self.fail("expected protocol name after 'impl'");
}
const protocol_name = self.tokenSlice(self.current);
self.advance();
// 'for' — note: 'for' is a keyword (kw_for), not an identifier
if (self.current.tag != .kw_for) {
return self.fail("expected 'for' after protocol name in impl block");
}
self.advance();
// Target type name
if (self.current.tag != .identifier) {
return self.fail("expected type name after 'for'");
}
const target_type = self.tokenSlice(self.current);
self.advance();
// Optional type params: impl Protocol for List($T)
var target_type_params = std.ArrayList(ast.StructTypeParam).empty;
if (self.current.tag == .l_paren) {
self.advance(); // skip '('
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (target_type_params.items.len > 0) {
try self.expect(.comma);
}
try self.expect(.dollar);
if (self.current.tag != .identifier) {
return self.fail("expected type parameter name after '$'");
}
const param_name = self.tokenSlice(self.current);
self.advance();
// Optional constraint — for now just use Type
const constraint = try self.createNode(self.current.loc.start, .{ .type_expr = .{ .name = "Type" } });
try target_type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint });
}
try self.expect(.r_paren);
}
try self.expect(.l_brace);
// Set struct type params context so method params can reference T without $
var tp_names = std.ArrayList([]const u8).empty;
for (target_type_params.items) |tp| try tp_names.append(self.allocator, tp.name);
const saved_struct_type_params = self.struct_type_params;
self.struct_type_params = tp_names.items;
defer self.struct_type_params = saved_struct_type_params;
var methods = std.ArrayList(*Node).empty;
while (self.current.tag != .r_brace and self.current.tag != .eof) {
// Method: name :: (params) -> type { body }
if (self.current.tag != .identifier) {
return self.fail("expected method name in impl block");
}
const method_start = self.current.loc.start;
const method_name = self.tokenSlice(self.current);
self.advance();
try self.expect(.colon_colon);
if (self.current.tag == .l_paren and self.isFunctionDef()) {
try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start));
} else {
return self.fail("expected function declaration in impl block");
}
}
try self.expect(.r_brace);
return try self.createNode(start_pos, .{ .impl_block = .{
.protocol_name = protocol_name,
.target_type = target_type,
.target_type_params = try target_type_params.toOwnedSlice(self.allocator),
.methods = try methods.toOwnedSlice(self.allocator),
} });
}
@@ -848,7 +1071,13 @@ pub const Parser = struct {
const param_name = self.tokenSlice(self.current);
const param_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
self.advance();
try self.expect(.colon);
// Optional type annotation: if no ':', infer type from context
if (self.current.tag != .colon) {
const inferred_node = try self.createNode(param_name_span.start, .{ .inferred_type = {} });
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = inferred_node });
continue;
}
self.advance(); // consume ':'
const is_variadic = self.current.tag == .dot_dot;
if (is_variadic) self.advance();
const param_type = try self.parseTypeExpr();
@@ -856,7 +1085,18 @@ pub const Parser = struct {
if (is_ct_param and param_type.data == .type_expr) {
const constraint_name = param_type.data.type_expr.name;
if (std.mem.eql(u8, constraint_name, "Type")) {
param_type.data = .{ .type_expr = .{ .name = param_name, .is_generic = true } };
// Parse optional protocol constraints: $T: Type/Eq/Hashable
var constraints = std.ArrayList([]const u8).empty;
while (self.current.tag == .slash) {
self.advance(); // skip '/'
if (self.current.tag != .identifier) {
return self.fail("expected protocol name after '/'");
}
try constraints.append(self.allocator, self.tokenSlice(self.current));
self.advance();
}
const pc = try constraints.toOwnedSlice(self.allocator);
param_type.data = .{ .type_expr = .{ .name = param_name, .is_generic = true, .protocol_constraints = pc } };
} else {
is_comptime_param = true;
}
@@ -906,8 +1146,13 @@ pub const Parser = struct {
for (generic_names.items) |gen_name| {
if (!seen.contains(gen_name)) {
try seen.put(gen_name, {});
// Propagate protocol constraints from the TypeExpr if present
const pc = if (param.type_expr.data == .type_expr)
param.type_expr.data.type_expr.protocol_constraints
else
&[_][]const u8{};
const type_constraint = self.createNode(param.type_expr.span.start, .{ .type_expr = .{ .name = "Type" } }) catch continue;
type_params.append(self.allocator, .{ .name = gen_name, .constraint = type_constraint }) catch {};
type_params.append(self.allocator, .{ .name = gen_name, .constraint = type_constraint, .protocol_constraints = pc }) catch {};
}
}
}
@@ -1476,6 +1721,11 @@ pub const Parser = struct {
self.advance();
return try self.createNode(start, .{ .identifier = .{ .name = name } });
},
.kw_closure => {
// `closure` keyword used as identifier in expressions (closure intrinsic call)
self.advance();
return try self.createNode(start, .{ .identifier = .{ .name = "closure" } });
},
.dot => {
self.advance();
// Anonymous struct literal: .{ ... }
@@ -1946,7 +2196,9 @@ pub const Parser = struct {
if (self.current.tag == .r_paren) break :blk true; // empty parens
if (self.current.tag != .identifier) break :blk false;
self.advance();
break :blk self.current.tag == .colon;
break :blk self.current.tag == .colon or
self.current.tag == .comma or
self.current.tag == .r_paren;
};
// Restore to '(' and scan past parens inline (not via peekPastParens which restores state)

View File

@@ -16,6 +16,7 @@ pub const SymbolKind = enum {
function,
enum_type,
struct_type,
protocol_type,
type_alias,
param,
namespace,
@@ -650,9 +651,10 @@ pub const Analyzer = struct {
}
// Built-in names that aren't declared in source
if (std.mem.eql(u8, name, "io")) return;
if (std.mem.eql(u8, name, "true") or std.mem.eql(u8, name, "false")) return;
if (std.mem.eql(u8, name, "cast")) return;
const builtins = [_][]const u8{ "io", "true", "false", "cast", "closure", "out", "size_of", "malloc", "free", "memcpy", "memset" };
for (builtins) |b| {
if (std.mem.eql(u8, name, b)) return;
}
try self.diagnostics.append(self.allocator, .{
.level = .warn,
@@ -844,6 +846,7 @@ pub const Analyzer = struct {
.param,
.match_arm,
.undef_literal,
.inferred_type,
.builtin_expr,
.foreign_expr,
.library_decl,
@@ -863,6 +866,30 @@ pub const Analyzer = struct {
.slice_expr,
.tuple_type_expr,
=> {},
.protocol_decl => |pd| {
try self.addSymbol(pd.name, .protocol_type, null, node.span);
// Recurse into default method bodies
for (pd.methods) |method| {
if (method.default_body) |body| {
try self.pushScope();
// `self` is implicit in protocol default methods
try self.addSymbol("self", .param, null, node.span);
for (method.param_names) |pname| {
try self.addSymbol(pname, .param, null, node.span);
}
try self.analyzeNode(body);
self.popScope();
}
}
},
.impl_block => |ib| {
// Each impl block gets its own scope so methods don't conflict across impls
try self.pushScope();
for (ib.methods) |method_node| {
try self.analyzeNode(method_node);
}
self.popScope();
},
.ufcs_alias => |ua| {
// Register the alias name as a function and resolve the target
try self.addSymbol(ua.name, .function, null, node.span);
@@ -1205,6 +1232,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
.param,
.match_arm,
.undef_literal,
.inferred_type,
.builtin_expr,
.foreign_expr,
.library_decl,
@@ -1228,6 +1256,21 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
.ufcs_alias,
.closure_type_expr,
=> {},
.protocol_decl => |pd| {
for (pd.methods) |method| {
if (method.default_body) |body| {
if (findNodeAtOffset(body, offset)) |found| return found;
}
for (method.params) |param| {
if (findNodeAtOffset(param, offset)) |found| return found;
}
}
},
.impl_block => |ib| {
for (ib.methods) |method_node| {
if (findNodeAtOffset(method_node, offset)) |found| return found;
}
},
.tuple_literal => |tl| {
for (tl.elements) |elem| {
if (findNodeAtOffset(elem.value, offset)) |found| return found;

View File

@@ -32,6 +32,10 @@ pub const Tag = enum {
kw_push, // push
kw_ufcs, // ufcs
kw_in, // in
kw_closure, // closure
kw_protocol, // protocol
kw_impl, // impl
kw_Self, // Self (in protocol declarations)
// Symbols
colon, // :
@@ -103,6 +107,7 @@ pub const Tag = enum {
hash_source, // #source (inside #import c { ... })
hash_define, // #define (inside #import c { ... })
hash_flags, // #flags (inside #import c { ... })
hash_inline, // #inline (protocol layout modifier)
triple_minus, // ---
// Special
@@ -169,7 +174,7 @@ pub const Tag = enum {
pub fn isTypeKeyword(tag: Tag) bool {
return switch (tag) {
.kw_f32, .kw_f64, .kw_Type => true,
.kw_f32, .kw_f64, .kw_Type, .kw_Self => true,
else => false,
};
}
@@ -215,6 +220,10 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "push", .kw_push },
.{ "ufcs", .kw_ufcs },
.{ "in", .kw_in },
.{ "closure", .kw_closure },
.{ "protocol", .kw_protocol },
.{ "impl", .kw_impl },
.{ "Self", .kw_Self },
});
pub fn getKeyword(bytes: []const u8) ?Tag {

View File

@@ -200,6 +200,36 @@ pub const Type = union(enum) {
};
}
/// Returns the canonical type name for this type, or null for complex types.
/// Used for looking up impl methods on non-struct types (e.g., s32.eq).
pub fn toName(self: Type) ?[]const u8 {
return switch (self) {
.signed => |w| switch (w) {
8 => "s8",
16 => "s16",
32 => "s32",
64 => "s64",
else => null,
},
.unsigned => |w| switch (w) {
8 => "u8",
16 => "u16",
32 => "u32",
64 => "u64",
else => null,
},
.f32 => "f32",
.f64 => "f64",
.boolean => "bool",
.string_type => "string",
.void_type => "void",
.struct_type => |n| n,
.enum_type => |n| n,
.union_type => |n| n,
else => null,
};
}
pub fn fromTypeExpr(node: *Node) ?Type {
if (node.data != .type_expr) return null;
return fromName(node.data.type_expr.name);