Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AST node IDs to types.Err for errorable expressions #862

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion common/types/err.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Error interface {
// Err type which extends the built-in go error and implements ref.Val.
type Err struct {
error
id int64
}

var (
Expand Down Expand Up @@ -58,7 +59,24 @@ var (
// NewErr creates a new Err described by the format string and args.
// TODO: Audit the use of this function and standardize the error messages and codes.
func NewErr(format string, args ...any) ref.Val {
return &Err{fmt.Errorf(format, args...)}
return &Err{error: fmt.Errorf(format, args...)}
}

// NewErrWithNodeID creates a new Err described by the format string and args.
// TODO: Audit the use of this function and standardize the error messages and codes.
func NewErrWithNodeID(id int64, format string, args ...any) ref.Val {
return &Err{error: fmt.Errorf(format, args...), id: id}
}

// LabelErrNode returns val unaltered it is not an Err or if the error has a non-zero
// AST node ID already present. Otherwise the id is added to the error for
// recovery with the Err.NodeID method.
func LabelErrNode(id int64, val ref.Val) ref.Val {
if err, ok := val.(*Err); ok && err.id == 0 {
err.id = id
return err
}
return val
}

// NoSuchOverloadErr returns a new types.Err instance with a no such overload message.
Expand Down Expand Up @@ -124,6 +142,11 @@ func (e *Err) Value() any {
return e.error
}

// NodeID returns the AST node ID of the expression that returned the error.
func (e *Err) NodeID() int64 {
return e.id
}

// Is implements errors.Is.
func (e *Err) Is(target error) bool {
return e.error.Error() == target.Error()
Expand Down
2 changes: 1 addition & 1 deletion common/types/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func (p *Registry) NewValue(structType string, fields map[string]ref.Val) ref.Va
}
err := msgSetField(msg, field, value)
if err != nil {
return &Err{err}
return &Err{error: err}
}
}
return p.NativeToValue(msg.Interface())
Expand Down
2 changes: 1 addition & 1 deletion common/types/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (s String) Match(pattern ref.Val) ref.Val {
}
matched, err := regexp.MatchString(pat.Value().(string), s.Value().(string))
if err != nil {
return &Err{err}
return &Err{error: err}
}
return Bool(matched)
}
Expand Down
50 changes: 26 additions & 24 deletions interpreter/interpretable.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (test *evalTestOnly) Eval(ctx Activation) ref.Val {
val, err := test.Resolve(ctx)
// Return an error if the resolve step fails
if err != nil {
return types.WrapErr(err)
return types.LabelErrNode(test.id, types.WrapErr(err))
}
if optVal, isOpt := val.(*types.Optional); isOpt {
return types.Bool(optVal.HasValue())
Expand Down Expand Up @@ -231,6 +231,7 @@ func (or *evalOr) Eval(ctx Activation) ref.Val {
} else {
err = types.MaybeNoSuchOverloadErr(val)
}
err = types.LabelErrNode(or.id, err)
}
}
}
Expand Down Expand Up @@ -273,6 +274,7 @@ func (and *evalAnd) Eval(ctx Activation) ref.Val {
} else {
err = types.MaybeNoSuchOverloadErr(val)
}
err = types.LabelErrNode(and.id, err)
}
}
}
Expand Down Expand Up @@ -377,7 +379,7 @@ func (zero *evalZeroArity) ID() int64 {

// Eval implements the Interpretable interface method.
func (zero *evalZeroArity) Eval(ctx Activation) ref.Val {
return zero.impl()
return types.LabelErrNode(zero.id, zero.impl())
}

// Function implements the InterpretableCall interface method.
Expand Down Expand Up @@ -421,14 +423,14 @@ func (un *evalUnary) Eval(ctx Activation) ref.Val {
// If the implementation is bound and the argument value has the right traits required to
// invoke it, then call the implementation.
if un.impl != nil && (un.trait == 0 || (!strict && types.IsUnknownOrError(argVal)) || argVal.Type().HasTrait(un.trait)) {
return un.impl(argVal)
return types.LabelErrNode(un.id, un.impl(argVal))
}
// Otherwise, if the argument is a ReceiverType attempt to invoke the receiver method on the
// operand (arg0).
if argVal.Type().HasTrait(traits.ReceiverType) {
return argVal.(traits.Receiver).Receive(un.function, un.overload, []ref.Val{})
return types.LabelErrNode(un.id, argVal.(traits.Receiver).Receive(un.function, un.overload, []ref.Val{}))
}
return types.NewErr("no such overload: %s", un.function)
return types.NewErrWithNodeID(un.id, "no such overload: %s", un.function)
}

// Function implements the InterpretableCall interface method.
Expand Down Expand Up @@ -479,14 +481,14 @@ func (bin *evalBinary) Eval(ctx Activation) ref.Val {
// If the implementation is bound and the argument value has the right traits required to
// invoke it, then call the implementation.
if bin.impl != nil && (bin.trait == 0 || (!strict && types.IsUnknownOrError(lVal)) || lVal.Type().HasTrait(bin.trait)) {
return bin.impl(lVal, rVal)
return types.LabelErrNode(bin.id, bin.impl(lVal, rVal))
}
// Otherwise, if the argument is a ReceiverType attempt to invoke the receiver method on the
// operand (arg0).
if lVal.Type().HasTrait(traits.ReceiverType) {
return lVal.(traits.Receiver).Receive(bin.function, bin.overload, []ref.Val{rVal})
return types.LabelErrNode(bin.id, lVal.(traits.Receiver).Receive(bin.function, bin.overload, []ref.Val{rVal}))
}
return types.NewErr("no such overload: %s", bin.function)
return types.NewErrWithNodeID(bin.id, "no such overload: %s", bin.function)
}

// Function implements the InterpretableCall interface method.
Expand Down Expand Up @@ -545,14 +547,14 @@ func (fn *evalVarArgs) Eval(ctx Activation) ref.Val {
// invoke it, then call the implementation.
arg0 := argVals[0]
if fn.impl != nil && (fn.trait == 0 || (!strict && types.IsUnknownOrError(arg0)) || arg0.Type().HasTrait(fn.trait)) {
return fn.impl(argVals...)
return types.LabelErrNode(fn.id, fn.impl(argVals...))
}
// Otherwise, if the argument is a ReceiverType attempt to invoke the receiver method on the
// operand (arg0).
if arg0.Type().HasTrait(traits.ReceiverType) {
return arg0.(traits.Receiver).Receive(fn.function, fn.overload, argVals[1:])
return types.LabelErrNode(fn.id, arg0.(traits.Receiver).Receive(fn.function, fn.overload, argVals[1:]))
}
return types.NewErr("no such overload: %s", fn.function)
return types.NewErrWithNodeID(fn.id, "no such overload: %s %d", fn.function, fn.id)
}

// Function implements the InterpretableCall interface method.
Expand Down Expand Up @@ -595,7 +597,7 @@ func (l *evalList) Eval(ctx Activation) ref.Val {
if l.hasOptionals && l.optionals[i] {
optVal, ok := elemVal.(*types.Optional)
if !ok {
return invalidOptionalElementInit(elemVal)
return types.LabelErrNode(l.id, invalidOptionalElementInit(elemVal))
}
if !optVal.HasValue() {
continue
Expand Down Expand Up @@ -645,7 +647,7 @@ func (m *evalMap) Eval(ctx Activation) ref.Val {
if m.hasOptionals && m.optionals[i] {
optVal, ok := valVal.(*types.Optional)
if !ok {
return invalidOptionalEntryInit(keyVal, valVal)
return types.LabelErrNode(m.id, invalidOptionalEntryInit(keyVal, valVal))
}
if !optVal.HasValue() {
delete(entries, keyVal)
Expand Down Expand Up @@ -705,7 +707,7 @@ func (o *evalObj) Eval(ctx Activation) ref.Val {
if o.hasOptionals && o.optionals[i] {
optVal, ok := val.(*types.Optional)
if !ok {
return invalidOptionalEntryInit(field, val)
return types.LabelErrNode(o.id, invalidOptionalEntryInit(field, val))
}
if !optVal.HasValue() {
delete(fieldVals, field)
Expand All @@ -715,7 +717,7 @@ func (o *evalObj) Eval(ctx Activation) ref.Val {
}
fieldVals[field] = val
}
return o.provider.NewValue(o.typeName, fieldVals)
return types.LabelErrNode(o.id, o.provider.NewValue(o.typeName, fieldVals))
}

func (o *evalObj) InitVals() []Interpretable {
Expand Down Expand Up @@ -921,7 +923,7 @@ func (e *evalWatchConstQual) Qualify(vars Activation, obj any) (any, error) {
out, err := e.ConstantQualifier.Qualify(vars, obj)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
val = types.LabelErrNode(e.ID(), types.WrapErr(err))
} else {
val = e.adapter.NativeToValue(out)
}
Expand All @@ -934,7 +936,7 @@ func (e *evalWatchConstQual) QualifyIfPresent(vars Activation, obj any, presence
out, present, err := e.ConstantQualifier.QualifyIfPresent(vars, obj, presenceOnly)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
val = types.LabelErrNode(e.ID(), types.WrapErr(err))
} else if out != nil {
val = e.adapter.NativeToValue(out)
} else if presenceOnly {
Expand Down Expand Up @@ -964,7 +966,7 @@ func (e *evalWatchAttrQual) Qualify(vars Activation, obj any) (any, error) {
out, err := e.Attribute.Qualify(vars, obj)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
val = types.LabelErrNode(e.ID(), types.WrapErr(err))
} else {
val = e.adapter.NativeToValue(out)
}
Expand All @@ -977,7 +979,7 @@ func (e *evalWatchAttrQual) QualifyIfPresent(vars Activation, obj any, presenceO
out, present, err := e.Attribute.QualifyIfPresent(vars, obj, presenceOnly)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
val = types.LabelErrNode(e.ID(), types.WrapErr(err))
} else if out != nil {
val = e.adapter.NativeToValue(out)
} else if presenceOnly {
Expand All @@ -1001,7 +1003,7 @@ func (e *evalWatchQual) Qualify(vars Activation, obj any) (any, error) {
out, err := e.Qualifier.Qualify(vars, obj)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
val = types.LabelErrNode(e.ID(), types.WrapErr(err))
} else {
val = e.adapter.NativeToValue(out)
}
Expand All @@ -1014,7 +1016,7 @@ func (e *evalWatchQual) QualifyIfPresent(vars Activation, obj any, presenceOnly
out, present, err := e.Qualifier.QualifyIfPresent(vars, obj, presenceOnly)
var val ref.Val
if err != nil {
val = types.WrapErr(err)
val = types.LabelErrNode(e.ID(), types.WrapErr(err))
} else if out != nil {
val = e.adapter.NativeToValue(out)
} else if presenceOnly {
Expand Down Expand Up @@ -1157,12 +1159,12 @@ func (cond *evalExhaustiveConditional) Eval(ctx Activation) ref.Val {
}
if cBool {
if tErr != nil {
return types.WrapErr(tErr)
return types.LabelErrNode(cond.id, types.WrapErr(tErr))
}
return cond.adapter.NativeToValue(tVal)
}
if fErr != nil {
return types.WrapErr(fErr)
return types.LabelErrNode(cond.id, types.WrapErr(fErr))
}
return cond.adapter.NativeToValue(fVal)
}
Expand Down Expand Up @@ -1202,7 +1204,7 @@ func (a *evalAttr) Adapter() types.Adapter {
func (a *evalAttr) Eval(ctx Activation) ref.Val {
v, err := a.attr.Resolve(ctx)
if err != nil {
return types.WrapErr(err)
return types.LabelErrNode(a.ID(), types.WrapErr(err))
}
return a.adapter.NativeToValue(v)
}
Expand Down
7 changes: 7 additions & 0 deletions interpreter/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,13 @@ func TestInterpreter(t *testing.T) {
if !types.IsError(got) || !strings.Contains(got.(*types.Err).String(), tc.err) {
t.Errorf("Got %v (%T), wanted error: %s", got, got, tc.err)
}
type nodeIDer interface {
NodeID() int64
}
nodeErr, ok := got.(nodeIDer)
if !ok || nodeErr.NodeID() == 0 {
t.Errorf("Did not get AST node ID from error: %#v", got)
}
} else if got.Equal(want) != types.True {
t.Errorf("Got %v, wanted %v", got, want)
}
Expand Down
2 changes: 1 addition & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (s *ConformanceServer) Eval(ctx context.Context, in *confpb.EvalRequest) (*
res, _, err := prg.Eval(args)
resultExprVal, err := RefValueToExprValue(res, err)
if err != nil {
return nil, fmt.Errorf("con't convert result: %s", err)
return nil, fmt.Errorf("can't convert result: %s", err)
}
return &confpb.EvalResponse{Result: resultExprVal}, nil
}
Expand Down