Skip to content

Commit

Permalink
cue: add IsNull for Decode
Browse files Browse the repository at this point in the history
Decode needs to check if CUE values are null,
but isn't interested in the constructed error value if they are not.
That work is wasted, so add a method that returns a bool and avoids it.

This noticeably affects internal/filetypes:

              │     old     │                 new                 │
              │   sec/op    │   sec/op     vs base                │
    CueFmtAll   268.7m ± 3%   230.5m ± 0%  -14.20% (p=0.000 n=10)

              │     old     │                 new                 │
              │ user-sec/op │ user-sec/op  vs base                │
    CueFmtAll   437.0m ± 7%   391.9m ± 7%  -10.31% (p=0.000 n=10)

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: I33d6327f26e7cabb878c0c444f34cc9bde4deb03
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1193405
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
  • Loading branch information
mvdan committed Apr 22, 2024
1 parent fdbd563 commit 6169eed
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 7 deletions.
4 changes: 2 additions & 2 deletions cue/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (d *decoder) decode(x reflect.Value, v Value, isPtr bool) {
switch x.Kind() {
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface:
// nullable types
if v.Null() == nil || !v.IsConcrete() {
if v.IsNull() || !v.IsConcrete() {
d.clear(x)
return
}
Expand All @@ -115,7 +115,7 @@ func (d *decoder) decode(x reflect.Value, v Value, isPtr bool) {
}
}

ij, it, x := indirect(x, v.Null() == nil)
ij, it, x := indirect(x, v.IsNull())

if ij != nil {
b, err := v.marshalJSON()
Expand Down
34 changes: 30 additions & 4 deletions cue/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,31 @@ func (v Value) Exists() bool {
return true
}

// isKind reports whether a value matches a particular kind.
// It is like checkKind, except that it doesn't construct an error value.
// Note that when v is bottom, the method always returns false.
func (v Value) isKind(ctx *adt.OpContext, want adt.Kind) bool {
if v.v == nil {
return false
}
x := v.eval(ctx)
if _, ok := x.(*adt.Bottom); ok {
return false
}
k := x.Kind()
if want != adt.BottomKind {
if k&want == adt.BottomKind {
return false
}
if !adt.IsConcrete(x) {
return false
}
}
return true
}

// checkKind returns a bottom error if a value does not match a particular kind,
// describing the reason why. Note that when v is bottom, it is always returned as-is.
func (v Value) checkKind(ctx *adt.OpContext, want adt.Kind) *adt.Bottom {
if v.v == nil {
return errNotExists
Expand Down Expand Up @@ -1311,10 +1336,11 @@ func (v Value) Null() error {
return nil
}

// // IsNull reports whether v is null.
// func (v Value) IsNull() bool {
// return v.Null() == nil
// }
// IsNull reports whether v is null.
func (v Value) IsNull() bool {
v, _ = v.Default()
return v.isKind(v.ctx(), adt.NullKind)
}

// Bool returns the bool value of v or false and an error if v is not a boolean.
func (v Value) Bool() (bool, error) {
Expand Down
8 changes: 7 additions & 1 deletion cue/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,14 @@ func TestNull(t *testing.T) {
}}
for _, tc := range testCases {
t.Run(tc.value, func(t *testing.T) {
err := getInstance(t, tc.value).Lookup("v").Null()
v := getInstance(t, tc.value).Lookup("v")
err := v.Null()
checkErr(t, err, tc.err, "init")
wantBool := err == nil
gotBool := v.IsNull()
if wantBool != gotBool {
t.Fatalf("IsNull reported %v, but Null reported: %v", gotBool, err)
}
})
}
}
Expand Down

0 comments on commit 6169eed

Please sign in to comment.