Backlog/lib/sdl3/parser/AGENTS.md

386 lines
9.4 KiB
Markdown

# Agent Solutions Guide: Zig 0.15 Issues
This document catalogs common issues encountered when working with Zig 0.15 and their solutions. Written for AI coding assistants to avoid repeating mistakes.
## Critical: ArrayList API Changed in Zig 0.15
### Problem
`std.ArrayList` is now an alias to `std.ArrayListUnmanaged` in Zig 0.15. The managed version has been removed.
### Old (Pre-0.15) Code - DOES NOT WORK
```zig
var list = std.ArrayList(u8).init(allocator);
defer list.deinit();
try list.append(item);
```
### New (0.15+) Code - CORRECT
```zig
// Empty initialization
var list = std.ArrayList(u8){};
defer list.deinit(allocator);
try list.append(allocator, item);
// Or with capacity
var list = try std.ArrayList(u8).initCapacity(allocator, 100);
defer list.deinit(allocator);
try list.append(allocator, item);
```
### Key Changes
1. **Initialization**: Use `{}` or `initCapacity()`, not `init()`
2. **All methods take allocator**: `append(allocator, item)` not `append(item)`
3. **Deinit takes allocator**: `deinit(allocator)` not `deinit()`
## AST Rendering API Changed
### Problem
The `ast.render()` function signature changed in Zig 0.15.
### Old Code - DOES NOT WORK
```zig
var ast = try std.zig.Ast.parse(allocator, source, .zig);
const output = try ast.render(allocator);
```
### New Code - CORRECT
```zig
var ast = try std.zig.Ast.parse(allocator, source, .zig);
const output = try ast.renderAlloc(allocator);
defer allocator.free(output);
```
### The API
- `renderAlloc(allocator)` - Returns allocated string
- `render(tree, gpa, writer, fixups)` - Low-level version for custom output
## Type Conversion: SDL Types to Zig
### Pointer Types
| C Type | Zig Type | Notes |
|--------|----------|-------|
| `const char *` | `[*c]const u8` | C string |
| `void *` | `?*anyopaque` | Nullable any pointer |
| `const void *` | `?*const anyopaque` | Const version |
| `SDL_Type *` | `?*Type` | Nullable pointer to opaque/struct |
| `const SDL_Type *` | `*const Type` | Non-null const pointer |
| `SDL_Type **` | `?*?*Type` | Output parameter (double pointer) |
| `SDL_Type *const *` | `[*c]*const Type` | Array of const pointers |
| `Uint32 *` | `*u32` | Output parameter (primitive) |
### Key Principles
1. **Non-nullable by default** for const pointers to structs
2. **Nullable (`?*`)** for pointers that can be NULL
3. **Use `*` not `[*c]`** when you know it's not a C-style array
4. **Double pointers**: `?*?*Type` for output parameters
## Function Signature Formatting
### Trailing Commas
Only use trailing commas for functions with **more than 3 parameters**. This triggers multi-line formatting.
```zig
// 1-3 parameters: single line, no trailing comma
pub fn foo(a: i32, b: i32, c: i32) void {}
// 4+ parameters: multi-line with trailing comma
pub fn bar(
a: i32,
b: i32,
c: i32,
d: i32,
) void {}
```
### Why?
- Trailing comma with no parameters: `(,)` is **syntax error**
- Trailing comma with 1-3 params: unnecessary, wastes vertical space
- Trailing comma with 4+ params: makes diffs cleaner, easier to read
## Method Organization
### Place Methods Inside Opaque Types
Functions where the first parameter is a pointer to an opaque type should be methods:
```zig
// Good - method syntax
pub const GPUDevice = opaque {
pub fn destroy(device: *GPUDevice) void {
c.SDL_DestroyGPUDevice(device);
}
};
// Usage: device.destroy()
// Bad - standalone function
pub fn destroyGPUDevice(device: ?*GPUDevice) void {
c.SDL_DestroyGPUDevice(device);
}
// Usage: destroyGPUDevice(device)
```
### Benefits
1. Cleaner API: `device.create()` vs `createGPUDevice(device)`
2. IDE autocomplete works better
3. Namespacing prevents naming conflicts
4. More idiomatic Zig
## Casting Guidelines
### When to Cast
| Scenario | Cast | Example |
|----------|------|---------|
| Opaque pointer | `@ptrCast` | `@ptrCast(device)` |
| Flags (packed struct) | `@bitCast` | `@bitCast(flags)` |
| Enum to int | `@intFromEnum` | `@intFromEnum(enum_val)` |
| Struct passed by value | None | Just pass it |
| Const pointer to struct | `@ptrCast` | `@ptrCast(info)` |
### Don't Over-Cast
```zig
// Bad - unnecessary cast for value type
fn setColor(color: FColor) void {
c.SDL_SetColor(@bitCast(color)); // Wrong!
}
// Good - no cast needed
fn setColor(color: FColor) void {
c.SDL_SetColor(color); // Correct
}
```
## StringHashMap Usage
### Correct Pattern
```zig
var map = std.StringHashMap(ValueType).init(allocator);
defer map.deinit(); // No allocator needed for deinit
try map.put("key", value);
const val = map.get("key");
```
### Iteration
```zig
var it = map.keyIterator();
while (it.next()) |key| {
// Use key.*
}
var it = map.valueIterator();
while (it.next()) |value| {
// Use value.* if needed
}
```
## Common Pitfalls
### 1. Forgetting Allocator in Unmanaged Collections
```zig
// Wrong
list.append(item);
// Right
list.append(allocator, item);
```
### 2. Using .init() on ArrayList
```zig
// Wrong
var list = std.ArrayList(u8).init(allocator);
// Right
var list = std.ArrayList(u8){};
// or
var list = try std.ArrayList(u8).initCapacity(allocator, size);
```
### 3. Not Checking AST Errors Before Rendering
```zig
// Wrong - will panic if there are errors
const output = try ast.renderAlloc(allocator);
// Right - check first
if (ast.errors.len > 0) {
// Handle errors
return error.ParseError;
}
const output = try ast.renderAlloc(allocator);
```
### 4. Incorrect Double Pointer Types
```zig
// Wrong - C-style for output params
texture: [*c]*GPUTexture
// Right - Zig optional pointers
texture: ?*?*GPUTexture
```
## Testing Patterns
### Simple Test
```zig
test "description" {
const result = try someFunction();
try std.testing.expectEqual(expected, result);
}
```
### Test with Allocator
```zig
test "with allocator" {
const allocator = std.testing.allocator;
const result = try allocateAndDoSomething(allocator);
defer allocator.free(result);
try std.testing.expectEqualStrings("expected", result);
}
```
## Build System Integration
### Adding Parser to Dependencies
```zig
// build.zig.zon
.dependencies = .{
.sdl3_parser = .{ .path = "parser/" },
},
// build.zig
const parser_dep = b.dependency("sdl3_parser", .{
.target = target,
.optimize = optimize,
});
const parser_exe = parser_dep.artifact("sdl-parser");
```
### Run Step
```zig
const run_parser = b.addRunArtifact(parser_exe);
run_parser.addFileArg(b.path("input.h"));
run_parser.addArg("--output=output.zig");
const step = b.step("generate", "Generate bindings");
step.dependOn(&run_parser.step);
```
## Quick Reference Card
```zig
// Collections
var list = std.ArrayList(T){};
defer list.deinit(allocator);
try list.append(allocator, item);
var map = std.StringHashMap(V).init(allocator);
defer map.deinit();
try map.put("key", value);
// AST
var ast = try std.zig.Ast.parse(allocator, source, .zig);
defer ast.deinit(allocator);
const formatted = try ast.renderAlloc(allocator);
defer allocator.free(formatted);
// Type Patterns
?*Type // Nullable pointer
*const Type // Non-null const pointer
?*?*Type // Output parameter
[*c]*const Type // C array of const pointers
// Casts
@ptrCast(ptr) // Pointers
@bitCast(value) // Packed structs, flags
@intFromEnum(e) // Enum to int
// No cast for value types!
```
## Version Info
- **Zig Version**: 0.15.2
- **Date**: 2025-01-22
- **SDL Version**: 3.2.0
## Issues Encountered During Mock Testing Implementation
### Issue 1: Build.addStaticLibrary Removed
**Problem**: Zig 0.15 removed `b.addStaticLibrary()` method.
**Error**:
```
error: no field or member function named 'addStaticLibrary' in 'Build'
```
**Solution**: Use `b.addLibrary()` with `.linkage = .static`:
```zig
// OLD - Does not work
const lib = b.addStaticLibrary(.{
.name = "mylib",
.target = target,
.optimize = optimize,
});
// NEW - Correct for Zig 0.15
const lib = b.addLibrary(.{
.name = "mylib",
.linkage = .static,
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
});
```
**Key change**: Must create `root_module` explicitly with target/optimize.
### Issue 2: C Mock Type Definitions
**Problem**: Generated C mocks referenced SDL types like `Uint32`, `SDL_Window`, `FColor` that weren't defined when using only stdint.h/stdbool.h.
**Error**:
```
error: unknown type name 'Uint32'
error: unknown type name 'SDL_GPUColorTargetInfo'
```
**Solution**: Include actual SDL headers in generated mocks:
```c
// OLD - Missing types
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
// NEW - Proper type definitions
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_gpu.h>
```
Then add SDL include path to C compilation:
```zig
mock_lib.addIncludePath(b.path("SDL/include"));
```
**Key insight**: Mocks should compile like real SDL implementation files, with full access to SDL type definitions.
### Issue 3: Testing Strategy
**Problem**: Initial testing with tiny `test_small.h` (3 declarations) didn't reveal real-world issues.
**Solution**: Test with full production header (SDL_gpu.h with 169 declarations) to:
- Verify parser handles large inputs
- Catch type definition issues
- Validate all declaration types work together
- Ensure build system scales
**Lesson**: Always test with realistic, production-sized inputs, not toy examples.
## References
- Zig 0.15 Release Notes: https://ziglang.org/download/0.15.0/release-notes.html
- Zig Standard Library Docs: https://ziglang.org/documentation/master/std/