From 22672f4e36be9401ab5606c3f4890d4924f7492f Mon Sep 17 00:00:00 2001 From: peterino Date: Mon, 8 Jun 2026 20:10:00 +0000 Subject: [PATCH] Delete AGENTS.md --- AGENTS.md | 607 ------------------------------------------------------ 1 file changed, 607 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 0791076..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,607 +0,0 @@ -# AGENTS.md - Solutions to Common Issues in Zig Development - -This document captures the main issues encountered during the zargs implementation and their solutions. This is valuable for AI coding agents and developers working with Zig 0.15+. - -## Table of Contents -1. [Zig 0.15 API Changes](#zig-015-api-changes) -2. [Comptime vs Runtime Issues](#comptime-vs-runtime-issues) -3. [Memory Management](#memory-management) -4. [Type System Challenges](#type-system-challenges) -5. [Build System Issues](#build-system-issues) -6. [Testing Strategies](#testing-strategies) - ---- - -## Zig 0.15 API Changes - -### Issue 1: Type Union Field Names Changed - -**Problem**: Code using `@typeInfo()` breaks with errors about union field names. - -```zig -// Zig 0.14 and earlier: -if (info == .Bool) { ... } - -// Zig 0.15: -if (info == .bool) { ... } // lowercase! -``` - -**Solution**: All `@typeInfo()` union fields are now lowercase: -- `.Bool` → `.bool` -- `.Int` → `.int` -- `.Pointer` → `.pointer` -- `.Enum` → `.@"enum"` -- `.Optional` → `.optional` - -**How to Fix**: Search your codebase for patterns like `== .Bool` or `.Int` and convert to lowercase. - ---- - -### Issue 2: Struct Field `default_value` → `default_value_ptr` - -**Problem**: `std.builtin.Type.StructField.default_value` doesn't exist. - -```zig -// Zig 0.14: -if (field.default_value) |val| { ... } - -// Zig 0.15: -if (field.default_value_ptr) |ptr| { ... } -``` - -**Solution**: Use `default_value_ptr` which is `?*const anyopaque`. Cast it to the field's type: - -```zig -if (field.default_value_ptr) |default_ptr| { - const value_ptr: *const T = @ptrCast(@alignCast(default_ptr)); - const value = value_ptr.*; - // Use value... -} -``` - ---- - -### Issue 3: ArrayList API Changes - -**Problem**: `ArrayList(T).init()` doesn't exist, `deinit()` signature changed. - -**Solution**: Use `ArrayListUnmanaged` for better control: - -```zig -// OLD (doesn't work in 0.15): -var list = std.ArrayList(T).init(allocator); -list.deinit(); - -// NEW (Zig 0.15): -var list = std.ArrayListUnmanaged(T){}; -try list.append(allocator, item); -list.deinit(allocator); -``` - -**Why**: `ArrayListUnmanaged` doesn't store the allocator, so `deinit()` needs it passed in. - ---- - -### Issue 4: Module System Changes - -**Problem**: Direct file imports cause "file exists in multiple modules" errors. - -```zig -// DON'T DO THIS: -const utils = @import("utils.zig"); - -// DO THIS: -const utils = @import("utils"); -``` - -**Solution**: In `build.zig`, set up proper module dependencies: - -```zig -const utils_mod = b.addModule("utils", .{ - .root_source_file = b.path("src/utils.zig"), - ... -}); - -const other_mod = b.addModule("other", .{ - .root_source_file = b.path("src/other.zig"), - .imports = &.{ - .{ .name = "utils", .module = utils_mod }, - }, -}); -``` - -Then import by module name, not file path. - ---- - -## Comptime vs Runtime Issues - -### Issue 5: Returning Pointers to Comptime Locals - -**Problem**: Functions that return pointers to comptime local variables fail when called from runtime contexts. - -```zig -// BROKEN: -pub fn toKebabCase(comptime name: []const u8) []const u8 { - comptime { - var result: [100]u8 = undefined; - // ... fill result ... - return result[0..len]; // ERROR: returning pointer to local! - } -} -``` - -**Error**: "function called at runtime cannot return value at comptime" - -**Root Cause**: Even though the function is `comptime`, if it's called from a runtime function (even a comptime parameter in a runtime function), Zig can't guarantee the returned pointer's lifetime. - -**Solution Options**: - -1. **Inline the logic**: Don't return pointers, inline the computation: -```zig -// In caller: -inline for (fields) |field| { - const field_name = field.name; // Already comptime - // Use field_name directly -} -``` - -2. **Return arrays, not slices**: If size is comptime-known: -```zig -pub fn toKebabCase(comptime name: []const u8) [computeLen(name)]u8 { - // Return array by value, not pointer -} -``` - -3. **Use comptime string literals**: Store in the struct directly: -```zig -const arg_name = if (user_meta.name) |custom| - custom // This is a string literal -else - field.name; // This is also a string literal -``` - -**Workaround We Used**: Temporarily disabled kebab-case conversion and used `field.name` directly (which is always a comptime string literal). - ---- - -### Issue 6: Comptime Arrays in Runtime Structures - -**Problem**: Storing comptime array slices in runtime-instantiated structs. - -```zig -pub fn extractAllFieldMetadata(comptime T: type) []const ArgumentMetadata { - comptime { - var metadata: [fields.len]ArgumentMetadata = undefined; - // Fill metadata... - return &metadata; // ERROR! - } -} -``` - -**Solution**: Don't return slices of comptime arrays. Instead: - -1. **Loop inline at call site**: -```zig -// Instead of: -const all_meta = extractAllFieldMetadata(T); -for (all_meta) |meta| { ... } - -// Do this: -inline for (type_info.@"struct".fields) |field| { - const meta = extractFieldMetadata(T, field); - // Use meta immediately -} -``` - -2. **Copy into runtime storage**: If you must store, allocate and copy: -```zig -const comptime_data = extractSomething(T); -const runtime_copy = try allocator.dupe(T, comptime_data); -``` - ---- - -## Memory Management - -### Issue 7: StringHashMap Key Ownership - -**Problem**: Using temporary strings as HashMap keys causes dangling pointers. - -```zig -// BROKEN: -const short_key = &[_]u8{short_char}; // Temporary! -try self.arguments.put(short_key, metadata); -// short_key is now dangling! -``` - -**Solution**: Allocate persistent keys: - -```zig -const short_key = try self.allocator.alloc(u8, 1); -short_key[0] = short_char; -try self.arguments.put(short_key, metadata); -// short_key is now owned by the HashMap -``` - -**Don't Forget Cleanup**: -```zig -pub fn deinit(self: *Self) void { - var key_iter = self.map.keyIterator(); - while (key_iter.next()) |key| { - if (key.len == 1) { // Our allocated short keys - self.allocator.free(key.*); - } - } - self.map.deinit(); -} -``` - ---- - -### Issue 8: HashMap Value vs Pointer Storage - -**Problem**: Storing pointers to comptime data in HashMaps. - -```zig -// BROKEN: -arguments: std.StringHashMap(*const ArgumentMetadata), - -const comptime_meta = extractMetadata(...); -try arguments.put(name, &comptime_meta); // Pointer to comptime data! -``` - -**Solution**: Store values, not pointers: - -```zig -arguments: std.StringHashMap(ArgumentMetadata), // Value, not pointer - -const comptime_meta = extractMetadata(...); -try arguments.put(name, comptime_meta); // Copy the value -``` - -**Accessing**: Use `getPtr()` to get a pointer to the stored value: - -```zig -pub fn getArgument(self: *const Self, name: []const u8) ?*const ArgumentMetadata { - if (self.arguments.getPtr(name)) |ptr| { - return ptr; - } - return null; -} -``` - ---- - -## Type System Challenges - -### Issue 9: Checking for Optional Types - -**Problem**: Detecting if a type is optional at comptime. - -**Solution**: -```zig -const is_optional = @typeInfo(T) == .optional; - -// To get the child type: -const ActualType = if (@typeInfo(T) == .optional) - @typeInfo(T).optional.child -else - T; -``` - -**Use Case**: Determining if an argument is required: -```zig -.required = user_meta.required orelse !is_optional, -``` - ---- - -### Issue 10: Enum Type Introspection - -**Problem**: Getting enum field names at comptime. - -**Solution**: -```zig -const enum_info = @typeInfo(EnumType).@"enum"; -for (enum_info.fields) |field| { - const name: []const u8 = field.name; - // name is a comptime string literal -} -``` - -**Converting from string to enum**: -```zig -inline for (enum_info.fields) |field| { - if (std.mem.eql(u8, str, field.name)) { - return @field(EnumType, field.name); - } -} -``` - ---- - -### Issue 11: Type Matching for Collision Detection - -**Problem**: Checking if two fields have compatible types. - -**Solution**: Use the `ArgumentType` enum for normalized comparison: - -```zig -pub const ArgumentType = enum { - bool, u8, u16, u32, u64, i8, i16, i32, i64, - string, string_list, enum_type, -}; - -// Extract type: -const arg_type = ArgumentType.fromZigType(field.type); - -// Compare: -if (existing.arg_type == new.arg_type) { - // Compatible! -} -``` - -This handles optionals automatically since `fromZigType` unwraps them. - ---- - -## Build System Issues - -### Issue 12: Module Dependency Cycles - -**Problem**: "file exists in multiple modules" errors. - -**Solution**: Create a clear dependency graph: - -```zig -// Base modules (no dependencies): -const base_mod = b.addModule("base", .{ - .root_source_file = b.path("src/base.zig"), -}); - -// Dependent modules: -const derived_mod = b.addModule("derived", .{ - .root_source_file = b.path("src/derived.zig"), - .imports = &.{ - .{ .name = "base", .module = base_mod }, - }, -}); -``` - -**Rule**: Never create circular dependencies. If module A imports B, B cannot import A. - ---- - -### Issue 13: Test Module Configuration - -**Problem**: Tests can't find imports. - -**Solution**: Set up test modules with all dependencies: - -```zig -const test_mod = b.createModule(.{ - .root_source_file = b.path("tests/test_foo.zig"), - .target = target, - .optimize = optimize, - .imports = &.{ - .{ .name = "foo", .module = foo_mod }, - .{ .name = "bar", .module = bar_mod }, - // Include ALL transitive dependencies - }, -}); - -const tests = b.addTest(.{ - .name = "foo-tests", - .root_module = test_mod, -}); -``` - ---- - -## Testing Strategies - -### Issue 14: Testing Comptime Functions - -**Problem**: Comptime functions can't be tested with runtime tests directly. - -**Solution**: Use comptime test blocks: - -```zig -test "comptime function" { - const result = comptime myComptimeFunc("input"); - try std.testing.expectEqualStrings("expected", result); -} -``` - -Or embed comptime assertions in the source: - -```zig -// In source file: -comptime { - const result = toKebabCase("camelCase"); - if (!std.mem.eql(u8, result, "camel-case")) { - @compileError("toKebabCase failed"); - } -} -``` - ---- - -### Issue 15: Memory Leak Detection in Tests - -**Problem**: Ensuring tests don't leak memory. - -**Solution**: Use `std.testing.allocator` and verify cleanup: - -```zig -test "no leaks" { - var registry = Registry.init(std.testing.allocator); - defer registry.deinit(); - - // Do stuff... - - // If deinit() doesn't free everything, test will fail -} -``` - -The testing allocator tracks all allocations and will fail if any aren't freed. - ---- - -## Best Practices Learned - -### 1. Comptime String Management - -**Rule**: Comptime strings are fine as long as they're string literals or stored by value in comptime structures. - -**DO**: -```zig -const name = field.name; // String literal -const meta = ArgumentMetadata{ - .arg_name = name, // Stores the pointer to literal -}; -``` - -**DON'T**: -```zig -const name = generateName(...); // Returns pointer to local -const meta = ArgumentMetadata{ - .arg_name = name, // Dangling pointer! -}; -``` - ---- - -### 2. Inline For Loops - -**Rule**: When iterating over comptime arrays from runtime contexts, use `inline for`: - -```zig -inline for (comptime_array) |item| { - // This unrolls at compile time - // Each iteration can use comptime values -} -``` - ---- - -### 3. Error Handling Patterns - -**Strategy**: Use error unions consistently: - -```zig -pub const Error = error{ ... }; - -pub fn function() Error!void { - // Can return any error from Error set -} - -// Caller: -function() catch |err| { - switch (err) { - error.Specific => { ... }, - else => { ... }, - } -}; -``` - ---- - -### 4. Arena Allocator for Parsing - -**Pattern**: Use an arena for temporary parsing data: - -```zig -var arena = std.heap.ArenaAllocator.init(parent_allocator); -defer arena.deinit(); -const allocator = arena.allocator(); - -// All allocations freed at once when arena is deinit'd -``` - ---- - -### 5. Type-Safe Unions - -**Pattern**: Use tagged unions for type-safe value storage: - -```zig -pub const Value = union(enum) { - bool: bool, - int: i64, - string: []const u8, - - pub fn asBool(self: Value) bool { - return switch (self) { - .bool => |b| b, - else => unreachable, - }; - } -}; -``` - ---- - -## Debugging Tips - -### 1. Comptime Error Messages - -When you get cryptic comptime errors: -- Look for "referenced by" chain -- Start at the deepest call in the chain -- Check if you're mixing comptime/runtime inappropriately - -### 2. Type Info Inspection - -Debug type problems: -```zig -const info = @typeInfo(T); -std.debug.print("Type info: {}\n", .{info}); -``` - -### 3. Build Cache Issues - -If build behavior is weird: -```bash -rm -rf .zig-cache zig-out -zig build -``` - -### 4. Test Isolation - -Run single test: -```bash -zig test src/file.zig --test-filter "test name" -``` - ---- - -## Summary Checklist - -When implementing similar features: - -- [ ] Check for Zig 0.15 API changes (lowercase type names, default_value_ptr, etc.) -- [ ] Avoid returning pointers to comptime locals -- [ ] Use `inline for` when iterating comptime arrays from runtime contexts -- [ ] Store values in HashMaps, not pointers to comptime data -- [ ] Allocate HashMap keys that need to persist -- [ ] Free allocated HashMap keys in deinit() -- [ ] Use `ArrayListUnmanaged` and pass allocator to deinit() -- [ ] Set up proper module dependencies in build.zig -- [ ] Test with `std.testing.allocator` to catch leaks -- [ ] Use arena allocators for temporary allocations - ---- - -## Resources - -- [Zig 0.15 Release Notes](https://ziglang.org/download/0.15.0/release-notes.html) -- [Zig Language Reference](https://ziglang.org/documentation/master/) -- [Zig Build System Documentation](https://ziglang.org/learn/build-system/) - ---- - -**Document Version**: 1.0 -**Last Updated**: 2026-01-22 -**Zig Version**: 0.15.2