-
Notifications
You must be signed in to change notification settings - Fork 348
Description
Summary
The approximateEqual function in error deduplication considers errors equal if they have the same position and path, ignoring message content. While this behavior may be rare in practice with pure CUE code, it can hide distinct errors when they occur.
Current Behavior
errors.Print() calls sanitize() which uses removeMultiples() to deduplicate errors:
// removeMultiples sorts a list and removes all but the first error per line.
func (p *list) removeMultiples() {
p.sort()
*p = slices.CompactFunc(*p, approximateEqual)
}
func approximateEqual(a, b Error) bool {
aPos := a.Position()
bPos := b.Position()
if !aPos.IsValid() || !bPos.IsValid() {
return a.Error() == b.Error() // Falls back to message comparison
}
return aPos.Compare(bPos) == 0 && slices.Compare(a.Path(), b.Path()) == 0
// Note: message content is NOT compared when positions are valid
}The comment says "removes all but the first error per line" but the implementation removes errors with the same position+path regardless of whether the messages are different.
Problem
When two distinct errors occur at the same position with the same path, only one is shown. This can happen when:
- Multiple validation phases generate different errors for the same AST node
- Tool integrations or custom code appends errors to an existing error list
- Future language features add new error types at existing positions
Reproducible Example
// Create two distinct errors at same valid position with same path
f := token.NewFile("test.cue", 1, 100)
f.SetLinesForContent([]byte("line1\nline2\nline3\n"))
pos := f.Pos(10, token.NoRelPos)
e1 := &testError{pos: pos, msg: "first error message", path: []string{"field"}}
e2 := &testError{pos: pos, msg: "second error message", path: []string{"field"}}
combined := errors.Append(nil, e1)
combined = errors.Append(combined, e2)
// Before sanitization: both errors present
errors.Errors(combined)
// [0]: first error message
// [1]: second error message
// After errors.Print(): only one shown
errors.Print(&buf, combined, nil)
// field: first error message:
// test.cue:2:5
//
// "second error message" is hiddenObservations
In practice with pure CUE code, this situation appears to be rare - CUE's compiler and evaluator generally avoid generating distinct errors at the exact same position+path. However:
- The code comment ("per line") doesn't match the implementation (per position+path)
- Duplicate identical errors ARE correctly deduplicated (e.g.,
leterrors) - The behavior could cause issues for tooling that combines error lists
Suggested Fix
Change approximateEqual to also compare messages when positions are valid:
func approximateEqual(a, b Error) bool {
aPos := a.Position()
bPos := b.Position()
if !aPos.IsValid() || !bPos.IsValid() {
return a.Error() == b.Error()
}
return aPos.Compare(bPos) == 0 &&
slices.Compare(a.Path(), b.Path()) == 0 &&
a.Error() == b.Error() // Also compare messages
}This would only deduplicate truly duplicate errors while preserving distinct ones.
We would need to verify the performance impact of this change.
Related
Found while investigating try expression error handling (issue #4019).