This commit is contained in:
agra
2026-02-23 13:45:44 +02:00
parent 1cc67f9b5a
commit 0cc7b69441
11 changed files with 1472 additions and 31 deletions

View File

@@ -68,6 +68,7 @@ pub const Node = struct {
foreign_expr: ForeignExpr,
library_decl: LibraryDecl,
function_type_expr: FunctionTypeExpr,
closure_type_expr: ClosureTypeExpr,
tuple_type_expr: TupleTypeExpr,
tuple_literal: TupleLiteral,
ufcs_alias: UfcsAlias,
@@ -427,6 +428,13 @@ pub const LibraryDecl = struct {
pub const FunctionTypeExpr = struct {
param_types: []const *Node,
param_names: ?[]const ?[]const u8 = null, // optional documentation names
return_type: ?*Node, // null = void return
};
pub const ClosureTypeExpr = struct {
param_types: []const *Node,
param_names: ?[]const ?[]const u8 = null, // optional documentation names
return_type: ?*Node, // null = void return
};

File diff suppressed because it is too large Load Diff

View File

@@ -390,14 +390,28 @@ pub const Parser = struct {
}
// Function type: (ParamTypes) -> ReturnType
// Tuple type: (T1, T2) or (T1) — no '->' after ')'
// Named params (documentation only): (name: Type, ...) -> ReturnType
if (self.current.tag == .l_paren) {
self.advance(); // skip '('
var param_types = std.ArrayList(*Node).empty;
var param_names = std.ArrayList(?[]const u8).empty;
var has_names = false;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (param_types.items.len > 0) {
try self.expect(.comma);
if (self.current.tag == .r_paren) break; // trailing comma ok
}
// Check for optional param name: `name: Type`
// An identifier followed by `:` (not `::` or `:=`) is a param name
if (self.current.tag == .identifier and self.peekNext() == .colon) {
const pname = self.tokenSlice(self.current);
self.advance(); // skip name
self.advance(); // skip ':'
try param_names.append(self.allocator, pname);
has_names = true;
} else {
try param_names.append(self.allocator, null);
}
try param_types.append(self.allocator, try self.parseTypeExpr());
}
try self.expect(.r_paren);
@@ -407,6 +421,7 @@ pub const Parser = struct {
const return_type = try self.parseTypeExpr();
return try self.createNode(start, .{ .function_type_expr = .{
.param_types = try param_types.toOwnedSlice(self.allocator),
.param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null,
.return_type = return_type,
} });
}
@@ -439,6 +454,42 @@ pub const Parser = struct {
}
}
// Closure type: Closure(params...) -> R
if (std.mem.eql(u8, name, "Closure") and self.current.tag == .l_paren) {
self.advance(); // skip '('
var param_types = std.ArrayList(*Node).empty;
var param_names = std.ArrayList(?[]const u8).empty;
var has_names = false;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (param_types.items.len > 0) {
try self.expect(.comma);
if (self.current.tag == .r_paren) break; // trailing comma ok
}
// Check for optional param name: `name: Type`
if (self.current.tag == .identifier and self.peekNext() == .colon) {
const pname = self.tokenSlice(self.current);
self.advance(); // skip name
self.advance(); // skip ':'
try param_names.append(self.allocator, pname);
has_names = true;
} else {
try param_names.append(self.allocator, null);
}
try param_types.append(self.allocator, try self.parseTypeExpr());
}
try self.expect(.r_paren);
var return_type: ?*Node = null;
if (self.current.tag == .arrow) {
self.advance();
return_type = try self.parseTypeExpr();
}
return try self.createNode(start, .{ .closure_type_expr = .{
.param_types = try param_types.toOwnedSlice(self.allocator),
.param_names = if (has_names) try param_names.toOwnedSlice(self.allocator) else null,
.return_type = return_type,
} });
}
// Parameterized type: Vector(N, T) or later generic struct instantiation
if (self.current.tag == .l_paren) {
self.advance(); // skip '('
@@ -1889,24 +1940,56 @@ pub const Parser = struct {
self.prev_end = saved_prev_end;
}
// Use shared paren-scanning, then check for lambda patterns
const tag = self.peekPastParens() orelse return false;
// Check upfront if parens look like function params (for block-body disambiguation)
const has_param_parens = blk: {
self.advance(); // skip '('
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;
};
// Restore to '(' and scan past parens inline (not via peekPastParens which restores state)
self.lexer = saved_lexer;
self.current = saved_current;
self.prev_end = saved_prev_end;
self.advance(); // skip '('
var depth: u32 = 1;
while (depth > 0 and self.current.tag != .eof) {
if (self.current.tag == .l_paren) depth += 1;
if (self.current.tag == .r_paren) depth -= 1;
if (depth > 0) self.advance();
}
if (self.current.tag != .r_paren) return false;
self.advance(); // skip ')' — now positioned on token after parens
const tag = self.current.tag;
// (params) => expr
if (tag == .fat_arrow) return true;
// (params) -> ReturnType => expr
// (params) -> ReturnType { stmts }
if (tag == .arrow) {
self.advance(); // skip '->'
// Skip past the return type tokens until we see '=>' or something unexpected
// Skip past the return type tokens until we see '=>', '{', or something unexpected
while (self.current.tag != .eof) {
if (self.current.tag == .fat_arrow) return true;
if (self.current.tag == .l_brace) return true;
if (self.current.tag == .identifier or self.current.tag.isTypeKeyword() or
self.current.tag == .dot or self.current.tag == .dollar or
self.current.tag == .l_bracket or self.current.tag == .r_bracket or
self.current.tag == .l_paren or self.current.tag == .r_paren or
self.current.tag == .comma or self.current.tag == .int_literal)
self.current.tag == .comma or self.current.tag == .int_literal or
self.current.tag == .star or self.current.tag == .question)
{
self.advance();
} else break;
}
return false;
}
// (params) { stmts } — block-body lambda
// Only if contents look like function params (have `:` type annotations or is empty `()`)
if (tag == .l_brace) {
return has_param_parens;
}
return false;
}
@@ -1915,15 +1998,22 @@ pub const Parser = struct {
const start = self.current.loc.start;
const params = try self.parseParams();
// Optional return type: (params) -> Type => expr
// Optional return type: (params) -> Type => expr OR (params) -> Type { stmts }
var return_type: ?*Node = null;
if (self.current.tag == .arrow) {
self.advance();
return_type = try self.parseTypeExpr();
}
try self.expect(.fat_arrow);
const body = try self.parseExpr();
// Two body forms:
// (params) => expr — expression lambda
// (params) { stmts } — block-body lambda
const body = if (self.current.tag == .l_brace)
try self.parseBlock()
else blk: {
try self.expect(.fat_arrow);
break :blk try self.parseExpr();
};
const type_params = try self.collectTypeParams(params);
return try self.createNode(start, .{ .lambda = .{
.params = params,

View File

@@ -848,6 +848,7 @@ pub const Analyzer = struct {
.foreign_expr,
.library_decl,
.function_type_expr,
.closure_type_expr,
.import_decl,
.c_import_decl,
.array_type_expr,
@@ -1225,6 +1226,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
.slice_expr,
.tuple_type_expr,
.ufcs_alias,
.closure_type_expr,
=> {},
.tuple_literal => |tl| {
for (tl.elements) |elem| {

View File

@@ -22,6 +22,7 @@ pub const Type = union(enum) {
many_pointer_type: ManyPointerTypeInfo,
vector_type: VectorTypeInfo,
function_type: FunctionTypeInfo,
closure_type: ClosureTypeInfo,
any_type,
optional_type: OptionalTypeInfo,
meta_type: MetaTypeInfo,
@@ -44,6 +45,11 @@ pub const Type = union(enum) {
return_type: *const Type,
};
pub const ClosureTypeInfo = struct {
param_types: []const Type,
return_type: *const Type,
};
pub const ArrayTypeInfo = struct {
element_name: []const u8,
length: u32,
@@ -95,6 +101,14 @@ pub const Type = union(enum) {
}
return info.return_type.eql(o.return_type.*);
},
.closure_type => |info| {
const o = other.closure_type;
if (info.param_types.len != o.param_types.len) return false;
for (info.param_types, o.param_types) |a, b| {
if (!a.eql(b)) return false;
}
return info.return_type.eql(o.return_type.*);
},
.optional_type => |info| std.mem.eql(u8, info.child_name, other.optional_type.child_name),
.meta_type => |info| std.mem.eql(u8, info.name, other.meta_type.name),
.tuple_type => |info| {
@@ -302,6 +316,21 @@ pub const Type = union(enum) {
};
}
pub fn isClosureType(self: Type) bool {
return switch (self) {
.closure_type => true,
else => false,
};
}
/// Returns true for both bare function pointers and closures
pub fn isCallable(self: Type) bool {
return switch (self) {
.function_type, .closure_type => true,
else => false,
};
}
pub fn isArray(self: Type) bool {
return switch (self) {
.array_type => true,
@@ -356,6 +385,7 @@ pub const Type = union(enum) {
.f64 => 64,
.boolean => 1,
.pointer_type, .many_pointer_type, .function_type => 64,
.closure_type => 128, // { ptr, ptr } = 16 bytes
else => 0,
};
}
@@ -506,6 +536,20 @@ pub const Type = union(enum) {
}
return try buf.toOwnedSlice(allocator);
},
.closure_type => |info| {
var buf = std.ArrayList(u8).empty;
try buf.appendSlice(allocator, "Closure(");
for (info.param_types, 0..) |pt, i| {
if (i > 0) try buf.appendSlice(allocator, ", ");
try buf.appendSlice(allocator, try pt.displayName(allocator));
}
try buf.append(allocator, ')');
if (!std.meta.eql(info.return_type.*, Type.void_type)) {
try buf.appendSlice(allocator, " -> ");
try buf.appendSlice(allocator, try info.return_type.displayName(allocator));
}
return try buf.toOwnedSlice(allocator);
},
.optional_type => |info| return fmtAlloc(allocator, "?{s}", .{info.child_name}),
.meta_type => |info| info.name,
.tuple_type => |info| {