feat: check for invalid returns inside of knots

This commit is contained in:
Brett Broadhurst 2026-04-15 15:05:14 -06:00
parent e004d00990
commit 55ddd2b7cf
Failed to generate hash of commit
4 changed files with 55 additions and 26 deletions

View file

@ -327,9 +327,17 @@ const GenIr = struct {
} }); } });
} }
fn addUnaryNode(gi: *GenIr, tag: Ir.Inst.Tag, arg: Ir.Inst.Ref) !Ir.Inst.Ref { fn addUnaryNode(
gi: *GenIr,
tag: Ir.Inst.Tag,
arg: Ir.Inst.Ref,
node: *const Ast.Node,
) !Ir.Inst.Ref {
return add(gi, .{ .tag = tag, .data = .{ return add(gi, .{ .tag = tag, .data = .{
.un = .{ .lhs = arg }, .un = .{
.lhs = arg,
.src_offset = @intCast(node.loc.start),
},
} }); } });
} }
@ -642,21 +650,21 @@ fn setCondBrPayload(
fn unaryOp( fn unaryOp(
gi: *GenIr, gi: *GenIr,
scope: *Scope, scope: *Scope,
expr_node: *const Ast.Node, node: *const Ast.Node,
op: Ir.Inst.Tag, op: Ir.Inst.Tag,
) InnerError!Ir.Inst.Ref { ) InnerError!Ir.Inst.Ref {
const data = expr_node.data.bin; const data = node.data.bin;
const lhs = try expr(gi, scope, data.lhs.?); const lhs = try expr(gi, scope, data.lhs.?);
return gi.addUnaryNode(op, lhs); return gi.addUnaryNode(op, lhs, node);
} }
fn binaryOp( fn binaryOp(
gi: *GenIr, gi: *GenIr,
scope: *Scope, scope: *Scope,
expr_node: *const Ast.Node, node: *const Ast.Node,
op: Ir.Inst.Tag, op: Ir.Inst.Tag,
) InnerError!Ir.Inst.Ref { ) InnerError!Ir.Inst.Ref {
const data = expr_node.data.bin; const data = node.data.bin;
const lhs = try expr(gi, scope, data.lhs.?); const lhs = try expr(gi, scope, data.lhs.?);
const rhs = try expr(gi, scope, data.rhs.?); const rhs = try expr(gi, scope, data.rhs.?);
return gi.addBinaryNode(op, lhs, rhs); return gi.addBinaryNode(op, lhs, rhs);
@ -771,7 +779,7 @@ fn identifier(
) InnerError!Ir.Inst.Ref { ) InnerError!Ir.Inst.Ref {
const str = try block.astgen.strFromNode(node); const str = try block.astgen.strFromNode(node);
if (scope.lookup(str.index)) |decl| { if (scope.lookup(str.index)) |decl| {
return block.addUnaryNode(.load, decl.inst_index.toRef()); return block.addUnaryNode(.load, decl.inst_index.toRef(), node);
} }
return block.addStrTok(.decl_ref, str.index, node.loc.start); return block.addStrTok(.decl_ref, str.index, node.loc.start);
} }
@ -1095,17 +1103,17 @@ fn content(
var suppress: bool = false; var suppress: bool = false;
const data = node.data.content; const data = node.data.content;
if (data.leading_glue) { if (data.leading_glue) {
_ = try block.addUnaryNode(.content_glue, .none); _ = try block.addUnaryNode(.content_glue, .none, node);
} }
for (data.items) |child_node| { for (data.items) |child_node| {
switch (child_node.tag) { switch (child_node.tag) {
.string_literal => { .string_literal => {
const result = try stringLiteral(block, child_node); const result = try stringLiteral(block, child_node);
_ = try block.addUnaryNode(.content_push, result); _ = try block.addUnaryNode(.content_push, result, child_node);
}, },
.inline_logic_expr => { .inline_logic_expr => {
const result = try inlineLogicExpr(block, scope, child_node); const result = try inlineLogicExpr(block, scope, child_node);
_ = try block.addUnaryNode(.content_push, result); _ = try block.addUnaryNode(.content_push, result, child_node);
}, },
.if_stmt => _ = { .if_stmt => _ = {
_ = try ifStmt(block, scope, child_node); _ = try ifStmt(block, scope, child_node);
@ -1125,12 +1133,12 @@ fn content(
} }
} }
if (data.trailing_glue or data.trailing_divert != null) { if (data.trailing_glue or data.trailing_divert != null) {
_ = try block.addUnaryNode(.content_glue, .none); _ = try block.addUnaryNode(.content_glue, .none, node);
} }
if (!ignore_divert) { if (!ignore_divert) {
if (data.trailing_divert) |trailing| { if (data.trailing_divert) |trailing| {
_ = try divertExpr(block, scope, trailing); _ = try divertExpr(block, scope, trailing);
_ = try block.addUnaryNode(.content_glue, .none); _ = try block.addUnaryNode(.content_glue, .none, node);
} }
} }
return suppress; return suppress;
@ -1143,7 +1151,7 @@ fn contentStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
supress = try content(gi, scope, item_node, false); supress = try content(gi, scope, item_node, false);
} }
if (!supress) { if (!supress) {
_ = try gi.addUnaryNode(.content_line, .void); _ = try gi.addUnaryNode(.content_line, .void, node);
} }
} }
@ -1175,7 +1183,7 @@ fn assignOp(
const lhs = if (scope.lookup(name_str.index)) |decl| blk: { const lhs = if (scope.lookup(name_str.index)) |decl| blk: {
const inst_ref = decl.inst_index.toRef(); const inst_ref = decl.inst_index.toRef();
_ = try gi.addUnaryNode(.load, inst_ref); _ = try gi.addUnaryNode(.load, inst_ref, node);
break :blk inst_ref; break :blk inst_ref;
} else blk: { } else blk: {
break :blk try gi.addStrTok(.decl_ref, name_str.index, node.loc.start); break :blk try gi.addStrTok(.decl_ref, name_str.index, node.loc.start);
@ -1250,7 +1258,7 @@ fn choiceStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void
_ = try blockStmt(&body_block, scope, branch_body); _ = try blockStmt(&body_block, scope, branch_body);
} }
if (!body_block.endsWithNoReturn()) { if (!body_block.endsWithNoReturn()) {
_ = try body_block.addUnaryNode(.implicit_ret, .none); _ = try body_block.addUnaryNode(.implicit_ret, .none, node);
} }
const lhs_body = block_1.instructionsSliceUpto(&block_2); const lhs_body = block_1.instructionsSliceUpto(&block_2);
@ -1438,10 +1446,10 @@ fn divertExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
// TODO: Revisit this // TODO: Revisit this
const str_slice = gi.astgen.tree.nodeSlice(lhs); const str_slice = gi.astgen.tree.nodeSlice(lhs);
if (std.mem.eql(u8, str_slice, "DONE")) { if (std.mem.eql(u8, str_slice, "DONE")) {
_ = try gi.addUnaryNode(.done, .none); _ = try gi.addUnaryNode(.done, .none, node);
return; return;
} else if (std.mem.eql(u8, str_slice, "END")) { } else if (std.mem.eql(u8, str_slice, "END")) {
_ = try gi.addUnaryNode(.exit, .none); _ = try gi.addUnaryNode(.exit, .none, node);
return; return;
} }
const callee = try calleeExpr(gi, scope, lhs); const callee = try calleeExpr(gi, scope, lhs);
@ -1496,7 +1504,7 @@ fn returnStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
const arg_inst = try expr(gi, scope, lhs); const arg_inst = try expr(gi, scope, lhs);
break :blk arg_inst; break :blk arg_inst;
} else .void; } else .void;
_ = try gi.addUnaryNode(.ret, ret_arg); _ = try gi.addUnaryNode(.ret, ret_arg, node);
} }
fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void { fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
@ -1588,7 +1596,7 @@ fn defaultBlock(
const knot_inst = try decl_scope.makePayloadNode(.decl_knot); const knot_inst = try decl_scope.makePayloadNode(.decl_knot);
try blockInner(&decl_scope, scope, data.items); try blockInner(&decl_scope, scope, data.items);
if (!decl_scope.endsWithNoReturn()) { if (!decl_scope.endsWithNoReturn()) {
_ = try decl_scope.addUnaryNode(.implicit_ret, .none); _ = try decl_scope.addUnaryNode(.implicit_ret, .none, body_node);
} }
var stub_scope = decl_scope.makeSubBlock(); var stub_scope = decl_scope.makeSubBlock();
@ -1634,7 +1642,8 @@ fn prototypeAndBody(
try blockStmt(gi, scope, body); try blockStmt(gi, scope, body);
} }
if (!gi.endsWithNoReturn()) { if (!gi.endsWithNoReturn()) {
_ = try gi.addUnaryNode(.implicit_ret, .none); // FIXME: Using `prototype_node` might break things.
_ = try gi.addUnaryNode(.implicit_ret, .none, prototype_node);
} }
return .{ .decl_name = decl_name }; return .{ .decl_name = decl_name };
} }

View file

@ -245,6 +245,7 @@ pub const Inst = struct {
}, },
un: struct { un: struct {
lhs: Ref, lhs: Ref,
src_offset: u32,
}, },
bin: struct { bin: struct {
lhs: Ref, lhs: Ref,

View file

@ -968,12 +968,18 @@ fn irChoiceBr(
fn irRet(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void { fn irRet(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
const data = sema.ir.instructions[@intFromEnum(inst)].data.un; const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
const lhs = sema.resolveInst(data.lhs); const lhs = sema.resolveInst(data.lhs);
if (lhs != .none) { const return_src: SrcLoc = .{ .src_offset = data.src_offset };
try builder.materialize(lhs); switch (builder.knot_tag) {
} else { .function => {
try builder.addByteOp(.stream_glue); if (lhs != .none) {
try builder.materialize(lhs);
} else {
try builder.addByteOp(.stream_glue);
}
try builder.addByteOp(.ret);
},
.knot => return sema.fail(return_src, "cannot return within a knot", .{}),
} }
try builder.addByteOp(.ret);
} }
fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void { fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void {

View file

@ -52,6 +52,19 @@ test "compiler: unknown global variable" {
); );
} }
test "compiler: invalid return" {
try testEqual(
\\=== knot ===
\\~ return 123
,
\\<STDIN>:2:3: error: cannot return within a knot
\\2 | ~ return 123
\\ | ^
\\
,
);
}
test "compiler: duplicate variable declarations" { test "compiler: duplicate variable declarations" {
try testEqual( try testEqual(
\\VAR a = 0 \\VAR a = 0