Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
b8c8600fea Implement pointer-based cyclic reference support in Bun's YAML parser
- Add pending_aliases map to track forward references
- Create placeholders for unresolved aliases and store pointers
- Resolve placeholders when anchors are defined
- Basic alias functionality works correctly
- Forward references partially implemented (debugging in progress)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 10:01:16 +00:00
6 changed files with 261 additions and 31 deletions

View File

@@ -498,7 +498,6 @@ src/copy_file.zig
src/crash_handler.zig
src/create/SourceFileProjectGenerator.zig
src/csrf.zig
src/css_scanner.zig
src/css/compat.zig
src/css/context.zig
src/css/css_internals.zig

41
simple_alias_test.js Normal file
View File

@@ -0,0 +1,41 @@
// Test basic alias functionality without cycles
console.log("Testing basic alias functionality...");
try {
// Test simple alias (non-cyclic)
const yaml1 = `
shared: &shared_data
name: shared
value: 42
first:
data: *shared_data
second:
data: *shared_data
`;
const result1 = Bun.YAML.parse(yaml1);
console.log("Non-cyclic alias test:");
console.log("- First data name:", result1.first.data.name);
console.log("- Second data name:", result1.second.data.name);
console.log("- Are they the same object?", result1.first.data === result1.second.data);
// Test forward reference (non-cyclic)
const yaml2 = `
first: *forward_ref
second: &forward_ref
name: forward
value: 123
`;
console.log("\nForward reference test:");
const result2 = Bun.YAML.parse(yaml2);
console.log("- First name:", result2.first.name);
console.log("- Second name:", result2.second.name);
console.log("- Are they the same object?", result2.first === result2.second);
} catch (err) {
console.log("❌ Error:", err.message);
console.log("Stack:", err.stack);
}

View File

@@ -262,6 +262,7 @@ comptime {
bun.assert(Line == Line);
}
pub fn Parser(comptime enc: Encoding) type {
const chars = enc.chars();
@@ -280,21 +281,10 @@ pub fn Parser(comptime enc: Encoding) type {
// anchors: Anchors,
anchors: bun.StringHashMap(Expr),
// aliases: PendingAliases,
pending_aliases: bun.StringHashMap(std.ArrayList(*Expr)),
tag_handles: bun.StringHashMap(void),
// const PendingAliases = struct {
// list: std.ArrayList(State),
// const State = struct {
// name: String.Range,
// index: usize,
// prop: enum { key, value },
// collection_node: *Node,
// };
// };
whitespace_buf: std.ArrayList(Whitespace),
stack_check: bun.StackCheck,
@@ -322,7 +312,7 @@ pub fn Parser(comptime enc: Encoding) type {
.block_indents = .init(allocator),
// .anchors = .{ .map = .init(allocator) },
.anchors = .init(allocator),
// .aliases = .{ .list = .init(allocator) },
.pending_aliases = .init(allocator),
.tag_handles = .init(allocator),
.whitespace_buf = .init(allocator),
.stack_check = .init(),
@@ -333,6 +323,14 @@ pub fn Parser(comptime enc: Encoding) type {
self.context.list.deinit();
self.block_indents.list.deinit();
self.anchors.deinit();
// Clean up pending aliases lists
var it = self.pending_aliases.iterator();
while (it.next()) |entry| {
entry.value_ptr.deinit();
}
self.pending_aliases.deinit();
self.tag_handles.deinit();
self.whitespace_buf.deinit();
// std.debug.assert(self.future == null);
@@ -677,6 +675,14 @@ pub fn Parser(comptime enc: Encoding) type {
var directives: std.ArrayList(Directive) = .init(self.allocator);
self.anchors.clearRetainingCapacity();
// Clear pending aliases
var it = self.pending_aliases.iterator();
while (it.next()) |entry| {
entry.value_ptr.deinit();
}
self.pending_aliases.clearRetainingCapacity();
self.tag_handles.clearRetainingCapacity();
var has_yaml_directive = false;
@@ -721,9 +727,46 @@ pub fn Parser(comptime enc: Encoding) type {
},
}
// Check for unresolved aliases
if (self.pending_aliases.count() > 0) {
return error.UnresolvedAlias;
}
return .{ .root = root, .directives = directives };
}
fn storeAnchor(self: *@This(), anchor_name: []const u8, expr: Expr) !void {
// Store the anchor name persistently for the anchors map
const persistent_anchor_name = try self.allocator.dupe(u8, anchor_name);
// Store the anchor
try self.anchors.put(persistent_anchor_name, expr);
// Resolve any pending aliases for this anchor (look for existing key)
var pending_key: []const u8 = undefined;
var found_pending = false;
var pending_iter = self.pending_aliases.iterator();
while (pending_iter.next()) |entry| {
if (std.mem.eql(u8, entry.key_ptr.*, anchor_name)) {
pending_key = entry.key_ptr.*;
found_pending = true;
break;
}
}
if (found_pending) {
const pending_list = self.pending_aliases.get(pending_key).?;
for (pending_list.items) |placeholder_ptr| {
placeholder_ptr.* = expr;
}
// Clear the pending list for this anchor
self.pending_aliases.getPtr(pending_key).?.deinit();
_ = self.pending_aliases.remove(pending_key);
}
}
fn parseFlowSequence(self: *@This()) ParseError!Expr {
const sequence_start = self.token.start;
const sequence_indent = self.token.indent;
@@ -1277,15 +1320,26 @@ pub fn Parser(comptime enc: Encoding) type {
return error.UnexpectedToken;
}
var copy = self.anchors.get(alias.slice(self.input)) orelse {
// we failed to find the alias, but it might be cyclic and
// and available later. to resolve this we need to check
// nodes for parent collection types. this alias is added
// to a list with a pointer to *Mapping or *Sequence, an
// index (and whether is key/value), and the alias name.
// then, when we actually have Node for the parent we
// fill in the data pointer at the index with the node.
return error.UnresolvedAlias;
const alias_name = alias.slice(self.input);
var copy = self.anchors.get(alias_name) orelse {
// Create a placeholder for the forward reference
const placeholder = try self.allocator.create(Expr);
placeholder.* = Expr.empty;
placeholder.loc = self.token.start.loc();
// Store the alias name in the allocator so it persists
const persistent_alias_name = try self.allocator.dupe(u8, alias_name);
// Add this placeholder to the pending list for this alias
var result = try self.pending_aliases.getOrPut(persistent_alias_name);
if (!result.found_existing) {
result.value_ptr.* = std.ArrayList(*Expr).init(self.allocator);
}
try result.value_ptr.append(placeholder);
try self.scan(.{});
break :node placeholder.*;
};
// update position from the anchor node to the alias node.
@@ -1320,7 +1374,7 @@ pub fn Parser(comptime enc: Encoding) type {
const implicit_key_anchors = node_props.implicitKeyAnchors(sequence_line);
if (implicit_key_anchors.key_anchor) |key_anchor| {
try self.anchors.put(key_anchor.slice(self.input), seq);
try self.storeAnchor(key_anchor.slice(self.input), seq);
}
const map = try self.parseBlockMapping(
@@ -1331,7 +1385,7 @@ pub fn Parser(comptime enc: Encoding) type {
);
if (implicit_key_anchors.mapping_anchor) |mapping_anchor| {
try self.anchors.put(mapping_anchor.slice(self.input), map);
try self.storeAnchor(mapping_anchor.slice(self.input), map);
}
return map;
@@ -1387,7 +1441,7 @@ pub fn Parser(comptime enc: Encoding) type {
const implicit_key_anchors = node_props.implicitKeyAnchors(mapping_line);
if (implicit_key_anchors.key_anchor) |key_anchor| {
try self.anchors.put(key_anchor.slice(self.input), map);
try self.storeAnchor(key_anchor.slice(self.input), map);
}
const parent_map = try self.parseBlockMapping(
@@ -1398,7 +1452,7 @@ pub fn Parser(comptime enc: Encoding) type {
);
if (implicit_key_anchors.mapping_anchor) |mapping_anchor| {
try self.anchors.put(mapping_anchor.slice(self.input), parent_map);
try self.storeAnchor(mapping_anchor.slice(self.input), parent_map);
}
}
break :node map;
@@ -1521,7 +1575,7 @@ pub fn Parser(comptime enc: Encoding) type {
const implicit_key_anchors = node_props.implicitKeyAnchors(scalar_line);
if (implicit_key_anchors.key_anchor) |key_anchor| {
try self.anchors.put(key_anchor.slice(self.input), implicit_key);
try self.storeAnchor(key_anchor.slice(self.input), implicit_key);
}
const mapping = try self.parseBlockMapping(
@@ -1532,7 +1586,7 @@ pub fn Parser(comptime enc: Encoding) type {
);
if (implicit_key_anchors.mapping_anchor) |mapping_anchor| {
try self.anchors.put(mapping_anchor.slice(self.input), mapping);
try self.storeAnchor(mapping_anchor.slice(self.input), mapping);
}
return mapping;
@@ -1559,7 +1613,7 @@ pub fn Parser(comptime enc: Encoding) type {
}
if (node_props.anchor()) |anchor| {
try self.anchors.put(anchor.slice(self.input), node);
try self.storeAnchor(anchor.slice(self.input), node);
}
return node;

View File

@@ -111,7 +111,7 @@ database:
});
});
test.todo("handles circular references with anchors and aliases", () => {
test("handles circular references with anchors and aliases", () => {
const yaml = `
parent: &ref
name: parent
@@ -125,6 +125,71 @@ parent: &ref
expect(result.parent.child.parent).toBe(result.parent);
});
test("handles complex circular references in arrays", () => {
const yaml = `
nodes: &nodeList
- id: 1
name: first
children: *nodeList
- id: 2
name: second
children: *nodeList
`;
const result = Bun.YAML.parse(yaml);
expect(result.nodes).toBeDefined();
expect(result.nodes.length).toBe(2);
expect(result.nodes[0].children).toBe(result.nodes);
expect(result.nodes[1].children).toBe(result.nodes);
// Verify referential equality
expect(result.nodes[0].children).toBe(result.nodes[1].children);
});
test("handles multiple nested circular references", () => {
const yaml = `
a: &a
name: nodeA
ref_b: &b
name: nodeB
ref_a: *a
ref_c: &c
name: nodeC
ref_a: *a
ref_b: *b
`;
const result = Bun.YAML.parse(yaml);
expect(result.a.name).toBe("nodeA");
expect(result.a.ref_b.name).toBe("nodeB");
expect(result.a.ref_b.ref_c.name).toBe("nodeC");
// Test cycles
expect(result.a.ref_b.ref_a).toBe(result.a);
expect(result.a.ref_b.ref_c.ref_a).toBe(result.a);
expect(result.a.ref_b.ref_c.ref_b).toBe(result.a.ref_b);
});
test("handles self-referencing objects", () => {
const yaml = `
recursive: &self
name: self
self: *self
`;
const result = Bun.YAML.parse(yaml);
expect(result.recursive.name).toBe("self");
expect(result.recursive.self).toBe(result.recursive);
expect(result.recursive.self.self).toBe(result.recursive);
});
test("handles circular references in flow style", () => {
const yaml = `
parent: &parent {name: parent, child: {name: child, parent: *parent}}
`;
const result = Bun.YAML.parse(yaml);
expect(result.parent.name).toBe("parent");
expect(result.parent.child.name).toBe("child");
expect(result.parent.child.parent).toBe(result.parent);
});
test("handles multiple documents", () => {
const yaml = `
---

42
test_cyclic_debug.js Normal file
View File

@@ -0,0 +1,42 @@
// Debug test for cyclic YAML parsing
console.log("Testing cyclic YAML parsing...");
try {
// Test simple circular reference
const yaml = `
parent: &ref
name: parent
child:
name: child
parent: *ref
`;
console.log("YAML input:", yaml);
const result = Bun.YAML.parse(yaml);
console.log("Raw result structure:");
console.log("- Has parent:", "parent" in result);
console.log("- Parent has name:", "name" in result.parent);
console.log("- Parent has child:", "child" in result.parent);
console.log("- Child has name:", "name" in result.parent.child);
console.log("- Child has parent:", "parent" in result.parent.child);
console.log("- Child parent type:", typeof result.parent.child.parent);
console.log("- Child parent value:", result.parent.child.parent);
console.log("- Child parent === undefined:", result.parent.child.parent === undefined);
console.log("- Child parent === null:", result.parent.child.parent === null);
// Test referential equality
if (result.parent.child.parent === result.parent) {
console.log("✅ Cyclic reference test PASSED");
} else if (result.parent.child.parent === undefined) {
console.log("❌ Cyclic reference is undefined - placeholder not resolved");
} else {
console.log("❌ Cyclic reference test FAILED - not the same object");
console.log("Expected:", result.parent);
console.log("Actual:", result.parent.child.parent);
}
} catch (err) {
console.log("❌ Error:", err.message);
console.log("Stack:", err.stack);
}

29
test_cyclic_yaml.js Normal file
View File

@@ -0,0 +1,29 @@
// Simple test for cyclic YAML parsing
console.log("Testing cyclic YAML parsing...");
try {
// Test simple circular reference
const yaml = `
parent: &ref
name: parent
child:
name: child
parent: *ref
`;
const result = Bun.YAML.parse(yaml);
console.log("Result:", result);
// Test referential equality
if (result.parent.child.parent === result.parent) {
console.log("✅ Cyclic reference test PASSED - referential equality maintained");
} else {
console.log("❌ Cyclic reference test FAILED - no referential equality");
console.log("parent:", result.parent);
console.log("child.parent:", result.parent.child.parent);
}
} catch (err) {
console.log("❌ Error:", err.message);
console.log("Stack:", err.stack);
}