cuelang.org/go@v0.13.0/internal/core/adt/simplify.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package adt
    16  
    17  import (
    18  	"bytes"
    19  	"strings"
    20  
    21  	"github.com/cockroachdb/apd/v3"
    22  
    23  	"cuelang.org/go/internal"
    24  )
    25  
    26  // SimplifyBounds collapses bounds if possible. The bound values must be
    27  // concrete. It returns nil if the bound values cannot be collapsed.
    28  //
    29  // k represents additional type constraints, such as `int`.
    30  func SimplifyBounds(ctx *OpContext, k Kind, x, y *BoundValue) Value {
    31  	xv := x.Value
    32  	yv := y.Value
    33  
    34  	cmp, xCat := opInfo(x.Op)
    35  	_, yCat := opInfo(y.Op)
    36  
    37  	// k := x.Kind() & y.Kind()
    38  
    39  	switch {
    40  	case xCat == yCat:
    41  		switch x.Op {
    42  		// NOTE: EqualOp should not happen, but include it defensively.
    43  		// Maybe an API would use it, for instance.
    44  		case EqualOp, NotEqualOp, MatchOp, NotMatchOp:
    45  			if test(ctx, EqualOp, xv, yv) {
    46  				return x
    47  			}
    48  			return nil // keep both bounds
    49  		}
    50  
    51  		// xCat == yCat && x.Op != NotEqualOp
    52  		// > a & >= b
    53  		//    > a   if a >= b
    54  		//    >= b  if a <  b
    55  		// > a & > b
    56  		//    > a   if a >= b
    57  		//    > b   if a <  b
    58  		// >= a & > b
    59  		//    >= a   if a > b
    60  		//    > b    if a <= b
    61  		// >= a & >= b
    62  		//    >= a   if a > b
    63  		//    >= b   if a <= b
    64  		// inverse is true as well.
    65  
    66  		// Tighten bound.
    67  		if test(ctx, cmp, xv, yv) {
    68  			return x
    69  		}
    70  		return y
    71  
    72  	case xCat == -yCat && k == StringKind:
    73  		if xCat == -1 {
    74  			x, y = y, x
    75  			xv, yv = yv, xv
    76  		}
    77  
    78  		a, aOK := xv.(*String)
    79  		b, bOK := yv.(*String)
    80  
    81  		if !aOK || !bOK {
    82  			break
    83  		}
    84  
    85  		switch diff := strings.Compare(a.Str, b.Str); diff {
    86  		case -1:
    87  		case 0:
    88  			if x.Op == GreaterEqualOp && y.Op == LessEqualOp {
    89  				return ctx.NewString(a.Str)
    90  			}
    91  			fallthrough
    92  
    93  		case 1:
    94  			return ctx.NewErrf("incompatible string bounds %v and %v", y, x)
    95  		}
    96  
    97  	case xCat == -yCat && k == BytesKind:
    98  		if xCat == -1 {
    99  			x, y = y, x
   100  			xv, yv = yv, xv
   101  		}
   102  
   103  		a, aOK := xv.(*Bytes)
   104  		b, bOK := yv.(*Bytes)
   105  
   106  		if !aOK || !bOK {
   107  			break
   108  		}
   109  
   110  		switch diff := bytes.Compare(a.B, b.B); diff {
   111  		case -1:
   112  		case 0:
   113  			if x.Op == GreaterEqualOp && y.Op == LessEqualOp {
   114  				return ctx.newBytes(a.B)
   115  			}
   116  			fallthrough
   117  
   118  		case 1:
   119  			return ctx.NewErrf("incompatible bytes bounds %v and %v", y, x)
   120  		}
   121  
   122  	case xCat == -yCat:
   123  		if xCat == -1 {
   124  			x, y = y, x
   125  			xv, yv = yv, xv
   126  		}
   127  		a, aOK := xv.(*Num)
   128  		b, bOK := yv.(*Num)
   129  
   130  		if !aOK || !bOK {
   131  			break
   132  		}
   133  
   134  		var d, lo, hi apd.Decimal
   135  		lo.Set(&a.X)
   136  		hi.Set(&b.X)
   137  		if k&FloatKind == 0 {
   138  			// Readjust bounds for integers.
   139  			if x.Op == GreaterEqualOp {
   140  				// >=3.4  ==>  >=4
   141  				_, _ = internal.BaseContext.Ceil(&lo, &a.X)
   142  			} else {
   143  				// >3.4  ==>  >3
   144  				_, _ = internal.BaseContext.Floor(&lo, &a.X)
   145  			}
   146  			if y.Op == LessEqualOp {
   147  				// <=2.3  ==>  <= 2
   148  				_, _ = internal.BaseContext.Floor(&hi, &b.X)
   149  			} else {
   150  				// <2.3   ==>  < 3
   151  				_, _ = internal.BaseContext.Ceil(&hi, &b.X)
   152  			}
   153  		}
   154  
   155  		cond, err := internal.BaseContext.Sub(&d, &hi, &lo)
   156  		if cond.Inexact() || err != nil {
   157  			break
   158  		}
   159  
   160  		// attempt simplification
   161  		// numbers
   162  		// >=a & <=b
   163  		//     a   if a == b
   164  		//     _|_ if b < a
   165  		// >=a & <b
   166  		//     _|_ if b <= a
   167  		// >a  & <=b
   168  		//     _|_ if b <= a
   169  		// >a  & <b
   170  		//     _|_ if b <= a
   171  
   172  		// integers
   173  		// >=a & <=b
   174  		//     a   if b-a == 0
   175  		//     _|_ if b < a
   176  		// >=a & <b
   177  		//     a   if b-a == 1
   178  		//     _|_ if b <= a
   179  		// >a  & <=b
   180  		//     b   if b-a == 1
   181  		//     _|_ if b <= a
   182  		// >a  & <b
   183  		//     a+1 if b-a == 2
   184  		//     _|_ if b <= a
   185  
   186  		if d.Negative {
   187  			return errIncompatibleBounds(ctx, k, x, y)
   188  		}
   189  		// [apd.Decimal.Int64] on `d = hi - lo` will error if it overflows an int64.
   190  		// This is pretty common with CUE bounds like int64, which expands to:
   191  		//
   192  		//     >=-9_223_372_036_854_775_808 & <=9_223_372_036_854_775_807
   193  		//
   194  		// Constructing that error is unfortunate as it allocates a few times
   195  		// and stringifies the number too, which also has a cost.
   196  		// Which is entirely unnecessary, as we don't use the error value at all.
   197  		// If we know the integer will have more than one digit, give up early.
   198  		if d.NumDigits() > 1 {
   199  			break
   200  		}
   201  		switch diff, err := d.Int64(); {
   202  		case diff == 1:
   203  			if k&FloatKind == 0 {
   204  				if x.Op == GreaterEqualOp && y.Op == LessThanOp {
   205  					return ctx.newNum(&lo, k&NumberKind, x, y)
   206  				}
   207  				if x.Op == GreaterThanOp && y.Op == LessEqualOp {
   208  					return ctx.newNum(&hi, k&NumberKind, x, y)
   209  				}
   210  				if x.Op == GreaterThanOp && y.Op == LessThanOp {
   211  					return ctx.NewErrf("incompatible integer bounds %v and %v", x, y)
   212  				}
   213  			}
   214  
   215  		case diff == 2:
   216  			if k&FloatKind == 0 && x.Op == GreaterThanOp && y.Op == LessThanOp {
   217  				_, _ = internal.BaseContext.Add(&d, d.SetInt64(1), &lo)
   218  				return ctx.newNum(&d, k&NumberKind, x, y)
   219  			}
   220  
   221  		case diff == 0 && err == nil:
   222  			if x.Op == GreaterEqualOp && y.Op == LessEqualOp {
   223  				return ctx.newNum(&lo, k&NumberKind, x, y)
   224  			}
   225  			return errIncompatibleBounds(ctx, k, x, y)
   226  		}
   227  
   228  	case x.Op == NotEqualOp:
   229  		if !test(ctx, y.Op, xv, yv) {
   230  			return y
   231  		}
   232  
   233  	case y.Op == NotEqualOp:
   234  		if !test(ctx, x.Op, yv, xv) {
   235  			return x
   236  		}
   237  	}
   238  	return nil
   239  }
   240  
   241  func errIncompatibleBounds(ctx *OpContext, k Kind, x, y *BoundValue) *Bottom {
   242  	if k == IntKind {
   243  		return ctx.NewErrf("incompatible integer bounds %v and %v", y, x)
   244  	} else {
   245  		return ctx.NewErrf("incompatible number bounds %v and %v", y, x)
   246  	}
   247  }
   248  
   249  func opInfo(op Op) (cmp Op, norm int) {
   250  	switch op {
   251  	case GreaterThanOp:
   252  		return GreaterEqualOp, 1
   253  	case GreaterEqualOp:
   254  		return GreaterThanOp, 1
   255  	case LessThanOp:
   256  		return LessEqualOp, -1
   257  	case LessEqualOp:
   258  		return LessThanOp, -1
   259  	case NotEqualOp:
   260  		return NotEqualOp, 0
   261  	case MatchOp:
   262  		return MatchOp, 2
   263  	case NotMatchOp:
   264  		return NotMatchOp, 3
   265  	}
   266  	panic("cue: unreachable")
   267  }
   268  
   269  func test(ctx *OpContext, op Op, a, b Value) bool {
   270  	if b, ok := BinOp(ctx, op, a, b).(*Bool); ok {
   271  		return b.B
   272  	}
   273  	return false
   274  }
   275  
   276  // SimplifyValidator simplifies non-bound validators.
   277  //
   278  // Currently this only checks for pure equality. In the future this can be used
   279  // to simplify certain builtin validators analogously to how we simplify bounds
   280  // now.
   281  func SimplifyValidator(ctx *OpContext, v, w Conjunct) (c Conjunct, ok bool) {
   282  	switch x := v.x.(type) {
   283  	case *BuiltinValidator:
   284  		switch y := w.x.(type) {
   285  		case *BuiltinValidator:
   286  			if x == y {
   287  				return v, true
   288  			}
   289  			if x.Builtin != y.Builtin || len(x.Args) != len(y.Args) {
   290  				return c, false
   291  			}
   292  			for i, a := range x.Args {
   293  				b := y.Args[i]
   294  				if v, ok := a.(*Vertex); ok {
   295  					v.Finalize(ctx)
   296  				}
   297  				if v, ok := b.(*Vertex); ok {
   298  					v.Finalize(ctx)
   299  				}
   300  				if !Equal(ctx, a, b, CheckStructural) {
   301  					return c, false
   302  				}
   303  			}
   304  			return v, true
   305  		}
   306  	}
   307  	return c, false
   308  }