Delete AGENTS.md
This commit is contained in:
parent
d980c5bfe9
commit
22672f4e36
607
AGENTS.md
607
AGENTS.md
|
|
@ -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
|
||||
Loading…
Reference in New Issue