Range() existed to recover byte offsets from Highlight subslices.
Now that ParserError carries an explicit Offset field, Range() is
unnecessary. Remove it along with the private subsliceOffset helper
in ast.go.
Tests now use perr.Offset directly and construct Range literals
for Shape() calls.
Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
Thread byte offset information through all error creation sites,
eliminating the need for SubsliceOffset to recover position from
pointer comparison.
Changes:
- Add Offset field to ParserError struct
- Add offset parameter to NewParserError
- Add Parser.offsetOf helper for suffix-length arithmetic
- Thread base offset through scanner functions (scanComment,
scanBasicString, scanMultilineBasicString, scanLiteralString,
scanMultilineLiteralString, scanWindowsNewline)
- Thread base offset through standalone functions (expect, hexToRune)
- Thread base offset through all decode functions (parseInteger,
parseFloat, parseLocalDate, parseLocalTime, parseLocalDateTime,
parseDateTime, checkAndRemoveUnderscores*)
- Update all unmarshaler call sites to pass value.Raw.Offset
- Update localtime.go UnmarshalText methods with base=0
- Update strict.go to populate Offset from key ranges
- Change wrapDecodeError to read de.Offset directly
- Change Utf8TomlValidAlreadyEscaped to return int index (-1 if valid)
instead of a byte subslice
- Unexport SubsliceOffset (now only used internally by Range())
This makes error positions self-describing: each ParserError carries its
own byte offset, so callers no longer need the original document slice
and address arithmetic to determine where an error occurred.
Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
Remove the private subsliceOffset methods from both parser.go and
errors.go. Replace them with a single exported SubsliceOffset function
in ast.go (next to the Range type it serves).
SubsliceOffset finds the byte offset by comparing element addresses:
&data[i] == &subslice[0]. This is well-defined Go pointer comparison
on elements of the same backing array.
This fixes the v2.3.0 regression (#1047) where the parser's
subsliceOffset used len(data) - len(b), which only works for suffix
slices, not arbitrary subslices like error highlights. It also removes
the reflect-based implementation from errors.go.
Fixes#1047
Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
* Reduce marshal and unmarshal overhead
Targeted optimizations to reduce performance overhead introduced by
recent feature additions and the unsafe removal.
Unmarshal:
- parseKeyval: access the node directly in the builder's slice to set
Raw, bypassing NodeAt which triggers a GC write barrier for the
nodes-pointer on every key-value expression.
- Iterator.Next: cache the *nodes slice dereference in a local variable
to avoid repeated pointer-to-slice indirection in the hot loop.
Marshal:
- Guard shouldOmitZero calls with an inlineable options.omitzero check.
shouldOmitZero has inlining cost 1145 (budget 80), so avoiding the
function call when omitzero is not set removes per-field overhead.
- Inline the isNil check in encodeMap. isNil has inlining cost 93
(budget 80), so expanding it at the single hot call site avoids
per-map-entry function call overhead.
Update README benchmarks.
Co-authored-by: Claude Opus 4.6 (1M context) <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]