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]