ParserError.Offset is now a plain exported int field, always set:
- The parser sets it via setErrOffset() when capturing parse errors
- strict.go sets it from the key's Raw range at construction
- wrapDecodeError computes it inline from cap(document) - cap(highlight)
This eliminates:
- The SetOffset/Offset() accessor methods and offsetValid flag
- The subsliceOffset fallback function in errors.go
- Any conditional logic around whether the offset is present
The offset is guaranteed by construction at every path that creates
or consumes a ParserError.
Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
Use cap(parent) - cap(subslice) to compute byte offsets between slices
that share a backing array. This is safe pure Go: subslicing preserves
the backing array and adjusts the capacity accordingly, so the
difference in capacities equals the byte offset.
This removes the reflect import from both errors.go and
unstable/parser.go, eliminating the last reflect-based pointer
arithmetic used for error position tracking.
Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
Instead of requiring downstream consumers to re-derive the byte offset
from pointer arithmetic on the Highlight slice, compute and cache the
offset inside the parser at error-capture time via setErrOffset().
This is safer because:
- The parser is the one place where the backing-array guarantee is known
to hold (Highlight is always a subslice of the parse buffer)
- Downstream consumers (wrapDecodeError) can use the cached offset
directly, avoiding the need for pointer comparison
- Errors created outside the parser (strict.go) set the offset from
existing Raw ranges, which are already correct by construction
Add ParserError.SetOffset/Offset methods for setting and retrieving the
cached offset. Update wrapDecodeError to prefer the cached offset when
available, falling back to subsliceOffset for backward compatibility.
Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
When marshaling a map with nil pointer values, the keys were being
silently dropped, breaking round-trip fidelity. For example:
map[string]*struct{}{"foo": nil}
Would produce an empty TOML document instead of "[foo]".
This change converts nil pointer values in maps to their zero values
(consistent with how nil pointers in slices are handled), allowing the
keys to be preserved as empty tables.
Nil interface values (map[string]any{"foo": nil}) are still skipped
since there's no type information to derive a zero value.
Fixes#975
Also, pin golangci-lint version to v2.8.0 in CI and document in AGENTS.md
- Explicitly set golangci-lint version in lint.yml to ensure consistent
behavior across CI runs
- Update AGENTS.md with instructions to use the same linter version locally
---------
Co-authored-by: Claude <noreply@anthropic.com>
Removes all unsafe operations from go-toml, making the codebase
fully safe Go code. The internal/danger package that contained
unsafe operations has been deleted.
Changes:
- Replace pointer-based node navigation with index-based navigation
- Node.next and Node.child now store absolute indices into the
backing nodes slice instead of relative offsets
- Add nodes pointer to Node and Iterator for safe navigation
- Replace danger.TypeID with reflect.Type for cache keys
- Delete internal/danger package entirely
Performance overhead is under 10% compared to the unsafe version,
which is acceptable for the safety and maintainability benefits.
[Cursor][claude-sonnet-4-20250514]
* Use pointers instead of copying around ast.Node
Node is a 56B struct that is constantly in the hot path. Passing nodes
around by copy had a cost that started to add up. This change replaces
them by pointers. Using unsafe pointer arithmetic and converting
sibling/child indexes to relative offsets, it removes the need to carry
around a pointer to the root of the tree. This saves 8B per Node. This
space will be used to store an extra []byte slice to provide contextual
error handling on all nodes, including the ones whose data is different
than the raw input (for example: strings with escaped characters), while
staying under the size of a cache line.
* Remove conditional
* Add Raw to track range in data for parsed values
* Simplify reference tracking