Skip to content

Commit 7114ed2

Browse files
authored
Preserve runtime error node IDs from Resolve (#1290)
When absoluteAttribute.Resolve() encounters a *types.Err variable value, return it directly rather than calling Unwrap(). The Unwrap call strips the Err wrapper and its node ID, causing callers to re-label the error with the observation site's ID instead of the originating expression's ID. Additionally, make WrapErr pass through existing *types.Err values rather than double-wrapping them. This ensures that LabelErrNode sees the original non-zero node ID and preserves it, regardless of the call site. Fixes #1191
1 parent d91350b commit 7114ed2

3 files changed

Lines changed: 29 additions & 1 deletion

File tree

common/types/err.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ func ValOrErr(val ref.Val, format string, args ...any) ref.Val {
113113

114114
// WrapErr wraps an existing Go error value into a CEL Err value.
115115
func WrapErr(err error) ref.Val {
116+
if err, ok := err.(*Err); ok {
117+
return err
118+
}
116119
return &Err{error: err}
117120
}
118121

interpreter/attributes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ func (a *absoluteAttribute) Resolve(vars Activation) (any, error) {
349349
obj, found := v.ResolveName(nm)
350350
if found {
351351
if celErr, ok := obj.(*types.Err); ok {
352-
return nil, celErr.Unwrap()
352+
return nil, celErr
353353
}
354354
obj, isOpt, err := applyQualifiers(v, obj, a.qualifiers)
355355
if err != nil {

interpreter/attributes_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,21 @@ func TestAttributeStateTracking(t *testing.T) {
11061106
),
11071107
out: types.NewUnknown(5, types.QualifyAttribute[string](types.NewAttributeTrail("a"), "b")),
11081108
},
1109+
{
1110+
expr: `['a', b.val, 'c'].filter(i, i + 'b' != 'ab')`,
1111+
vars: []*decls.VariableDecl{
1112+
decls.NewVariable("b", types.NewMapType(types.StringType, types.DynType)),
1113+
},
1114+
in: partialActivation(
1115+
map[string]any{
1116+
"b": map[string]any{
1117+
"val": 1,
1118+
},
1119+
},
1120+
),
1121+
// Error node 9 corresponds to the `i + 'b'` expression
1122+
out: types.LabelErrNode(9, types.NoSuchOverloadErr()),
1123+
},
11091124
}
11101125
for _, test := range tests {
11111126
tc := test
@@ -1170,9 +1185,19 @@ func TestAttributeStateTracking(t *testing.T) {
11701185
if !reflect.DeepEqual(tc.out, out) {
11711186
t.Errorf("got %v, wanted %v", out, tc.out)
11721187
}
1188+
} else if types.IsError(tc.out) && types.IsError(out) {
1189+
if tc.out.(*types.Err).Error() != out.(*types.Err).Error() {
1190+
t.Errorf("got %v, wanted %v", out, tc.out)
1191+
}
11731192
} else if tc.out.Equal(out) != types.True {
11741193
t.Errorf("got %v, wanted %v", out, tc.out)
11751194
}
1195+
if err, isErr := out.(*types.Err); isErr && err.NodeID() != 0 {
1196+
wantErr := tc.out.(*types.Err)
1197+
if err.NodeID() != wantErr.NodeID() {
1198+
t.Errorf("err.NodeID() got %d, wanted %d", err.NodeID(), wantErr.NodeID())
1199+
}
1200+
}
11761201
for id, val := range tc.state {
11771202
stVal, found := holder.st.Value(id)
11781203
if !found {

0 commit comments

Comments
 (0)