From 96ac48eb74857e5cb641a46c107c5d08bf5871d2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 12 Apr 2026 17:25:32 +0000 Subject: [PATCH] Remove optional offset and fallback, guarantee offset by construction 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 --- errors.go | 11 +---------- errors_test.go | 1 + strict.go | 14 ++++++-------- unstable/parser.go | 36 +++++++++++------------------------- unstable/parser_test.go | 10 +++------- 5 files changed, 22 insertions(+), 50 deletions(-) diff --git a/errors.go b/errors.go index a7f6960..9d86e13 100644 --- a/errors.go +++ b/errors.go @@ -99,10 +99,7 @@ func (e *DecodeError) Key() Key { // //nolint:funlen func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError { - offset, ok := de.Offset() - if !ok { - offset = subsliceOffset(document, de.Highlight) - } + offset := cap(document) - cap(de.Highlight) errMessage := de.Error() errLine, errColumn := positionAtEnd(document[:offset]) @@ -264,9 +261,3 @@ func positionAtEnd(b []byte) (row int, column int) { return row, column } - -// subsliceOffset returns the byte offset of subslice within data. -// subslice must share the same backing array as data. -func subsliceOffset(data []byte, subslice []byte) int { - return cap(data) - cap(subslice) -} diff --git a/errors_test.go b/errors_test.go index 22220a9..35c9205 100644 --- a/errors_test.go +++ b/errors_test.go @@ -172,6 +172,7 @@ line 5`, err := wrapDecodeError(doc, &unstable.ParserError{ Highlight: hl, Message: e.msg, + Offset: start, }) var derr *DecodeError diff --git a/strict.go b/strict.go index 2ee7900..2dbd98c 100644 --- a/strict.go +++ b/strict.go @@ -55,13 +55,12 @@ func (s *strict) MissingTable(node *unstable.Node) { } highlight, offset := s.keyLocation(node) - pe := unstable.ParserError{ + s.missing = append(s.missing, unstable.ParserError{ Highlight: highlight, Message: "missing table", Key: s.key.Key(), - } - pe.SetOffset(offset) - s.missing = append(s.missing, pe) + Offset: offset, + }) } func (s *strict) MissingField(node *unstable.Node) { @@ -70,13 +69,12 @@ func (s *strict) MissingField(node *unstable.Node) { } highlight, offset := s.keyLocation(node) - pe := unstable.ParserError{ + s.missing = append(s.missing, unstable.ParserError{ Highlight: highlight, Message: "missing field", Key: s.key.Key(), - } - pe.SetOffset(offset) - s.missing = append(s.missing, pe) + Offset: offset, + }) } func (s *strict) Error(doc []byte) error { diff --git a/unstable/parser.go b/unstable/parser.go index 82dc5a9..2ee121c 100644 --- a/unstable/parser.go +++ b/unstable/parser.go @@ -18,8 +18,9 @@ type ParserError struct { Message string Key []string // optional - offset int - offsetValid bool + // Offset is the byte offset of Highlight within the document. + // Set by the parser when the error is captured. + Offset int } // Error is the implementation of the error interface. @@ -27,21 +28,6 @@ func (e *ParserError) Error() string { return e.Message } -// SetOffset records the byte offset of the error highlight within the -// document. Used by the parser to cache position information so -// downstream consumers don't need to re-derive it from pointers. -func (e *ParserError) SetOffset(offset int) { - e.offset = offset - e.offsetValid = true -} - -// Offset returns the byte offset of the error highlight within the -// document, if it was previously set by the parser. The boolean -// indicates whether the offset is valid. -func (e *ParserError) Offset() (int, bool) { - return e.offset, e.offsetValid -} - // NewParserError is a convenience function to create a ParserError // // Warning: Highlight needs to be a subslice of Parser.data, so only slices @@ -145,11 +131,15 @@ func (p *Parser) NextExpression() bool { p.left, p.err = p.parseNewline(p.left) } - if len(p.left) == 0 || p.err != nil { + if p.err != nil { p.setErrOffset() return false } + if len(p.left) == 0 { + return false + } + p.ref, p.left, p.err = p.parseExpression(p.left) if p.err != nil { @@ -176,9 +166,8 @@ func (p *Parser) Error() error { return p.err } -// setErrOffset computes and caches the byte offset of the error's -// highlight within p.data, so downstream consumers can use it -// without pointer arithmetic. +// setErrOffset sets the byte offset on the parser error from the +// highlight's position within p.data. func (p *Parser) setErrOffset() { if p.err == nil { return @@ -187,10 +176,7 @@ func (p *Parser) setErrOffset() { if !errors.As(p.err, &perr) { return } - if perr.offsetValid || len(perr.Highlight) == 0 { - return - } - perr.SetOffset(p.subsliceOffset(perr.Highlight)) + perr.Offset = p.subsliceOffset(perr.Highlight) } // Position describes a position in the input. diff --git a/unstable/parser_test.go b/unstable/parser_test.go index 8012393..7785962 100644 --- a/unstable/parser_test.go +++ b/unstable/parser_test.go @@ -766,7 +766,7 @@ func TestErrorHighlightPositions(t *testing.T) { } } -func TestParserError_CachedOffset(t *testing.T) { +func TestParserError_Offset(t *testing.T) { examples := []struct { desc string input string @@ -803,12 +803,8 @@ func TestParserError_CachedOffset(t *testing.T) { if !errors.As(err, &perr) { t.Fatalf("expected ParserError, got %T", err) } - offset, ok := perr.Offset() - if !ok { - t.Fatal("expected offset to be set") - } - if offset != e.wantOffset { - t.Errorf("cached offset: got %d, want %d", offset, e.wantOffset) + if perr.Offset != e.wantOffset { + t.Errorf("offset: got %d, want %d", perr.Offset, e.wantOffset) } }) }