Made undefined variables return an error
All checks were successful
Build / build (push) Successful in 2m5s

Previously undefined variables would be set to nil, which introduced subtle errors.
Changed this so that referencing an undefined variable will produce an error.
This behaviour can changed as part of the instance configuration by declaring a missing-var handler.
This commit is contained in:
Leon Mika 2025-01-28 09:25:52 +11:00
parent 2d56517de9
commit c433e4bf53
5 changed files with 82 additions and 3 deletions

View file

@ -186,7 +186,16 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec
if v, ok := ec.getVar(*n.Var); ok { if v, ok := ec.getVar(*n.Var); ok {
return v, nil return v, nil
} }
return nil, nil
if e.inst.missingVarHandler != nil {
dv, err := e.inst.missingVarHandler(ctx, *n.Var)
if err != nil {
return nil, err
}
return fromGoValue(dv)
}
return nil, errors.New("undefined variable: " + *n.Var)
case n.MaybeSub != nil: case n.MaybeSub != nil:
sub := n.MaybeSub.Sub sub := n.MaybeSub.Sub
if sub == nil { if sub == nil {

View file

@ -11,6 +11,7 @@ import (
type Inst struct { type Inst struct {
out io.Writer out io.Writer
missingBuiltinHandler MissingBuiltinHandler missingBuiltinHandler MissingBuiltinHandler
missingVarHandler MissingVarHandler
echoPrinter EchoPrinter echoPrinter EchoPrinter
rootEC *evalCtx rootEC *evalCtx
@ -30,6 +31,12 @@ func WithMissingBuiltinHandler(handler MissingBuiltinHandler) InstOption {
} }
} }
func WithMissingVarHandler(handler MissingVarHandler) InstOption {
return func(i *Inst) {
i.missingVarHandler = handler
}
}
func WithModule(module Module) InstOption { func WithModule(module Module) InstOption {
return func(i *Inst) { return func(i *Inst) {
for name, builtin := range module.Builtins { for name, builtin := range module.Builtins {

View file

@ -119,6 +119,64 @@ func TestInst_Eval(t *testing.T) {
} }
} }
func TestInst_MissingVarHandler(t *testing.T) {
tests := []struct {
desc string
expr string
handler ucl.MissingVarHandler
want any
wantErr string
}{
{
desc: "default - error",
expr: `$bla`,
wantErr: "undefined variable: bla",
},
{
desc: "handler - set to nil",
handler: func(ctx context.Context, name string) (any, error) {
return nil, nil
},
expr: `$bla`,
want: nil,
},
{
desc: "handler - set to a string 1",
handler: func(ctx context.Context, name string) (any, error) {
return "I am " + name, nil
},
expr: `$bla`,
want: "I am bla",
},
{
desc: "handler - set to a string 2",
handler: func(ctx context.Context, name string) (any, error) {
return "I am " + name, nil
},
expr: `$something`,
want: "I am something",
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
ctx := context.Background()
outW := bytes.NewBuffer(nil)
inst := ucl.New(ucl.WithOut(outW), ucl.WithTestBuiltin(), ucl.WithMissingVarHandler(tt.handler))
res, err := inst.Eval(ctx, tt.expr)
if tt.wantErr != "" {
assert.Error(t, err)
assert.Equal(t, err.Error(), tt.wantErr)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, res)
}
})
}
}
var parseComments1 = ` var parseComments1 = `
proc lookup { |file| proc lookup { |file|
foreach { |toks| foreach { |toks|

View file

@ -197,7 +197,9 @@ func TestBuiltins_If(t *testing.T) {
ctx := context.Background() ctx := context.Background()
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := New(WithOut(outW), WithTestBuiltin()) inst := New(WithOut(outW), WithTestBuiltin(), WithMissingVarHandler(func(ctx context.Context, name string) (any, error) {
return nil, nil
}))
err := evalAndDisplay(ctx, inst, tt.expr) err := evalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)
@ -387,7 +389,9 @@ func TestBuiltins_Try(t *testing.T) {
ctx := context.Background() ctx := context.Background()
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := New(WithOut(outW), WithTestBuiltin()) inst := New(WithOut(outW), WithTestBuiltin(), WithMissingVarHandler(func(ctx context.Context, name string) (any, error) {
return nil, nil
}))
err := evalAndDisplay(ctx, inst, tt.expr) err := evalAndDisplay(ctx, inst, tt.expr)
if tt.wantErr != "" { if tt.wantErr != "" {

View file

@ -11,6 +11,7 @@ import (
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error) type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
type MissingBuiltinHandler func(ctx context.Context, name string, args CallArgs) (any, error) type MissingBuiltinHandler func(ctx context.Context, name string, args CallArgs) (any, error)
type MissingVarHandler func(ctx context.Context, name string) (any, error)
type CallArgs struct { type CallArgs struct {
args invocationArgs args invocationArgs