| // Copyright 2020 CUE Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package subsume defines various subsumption relations. |
| package subsume |
| |
| import ( |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/internal" |
| "cuelang.org/go/internal/core/adt" |
| ) |
| |
| // Profile configures the type of subsumption. One should typically use one |
| // of the preconfigured profiles. |
| type Profile struct { |
| // Final indicates subsumption should only consider fields that are relevant |
| // to data mode, and ignore definitions, hidden fields, pattern constraints |
| // and additional constraints. |
| Final bool |
| |
| // Defaults indicate that default values should be used for the subsumed |
| // value. |
| Defaults bool |
| |
| // Ignore optional fields. |
| IgnoreOptional bool |
| |
| // IgnoreClosedness ignores closedness of structs and is used for comparing |
| // APIs. |
| IgnoreClosedness bool |
| } |
| |
| var CUE = Profile{} |
| |
| // Final checks subsumption interpreting the subsumed value as data. |
| var Final = Profile{ |
| Final: true, |
| Defaults: true, |
| } |
| |
| // FinalOpen exists as an artifact of the old API. One should probably not use |
| // this. |
| var FinalOpen = Profile{ |
| Final: true, |
| Defaults: true, |
| IgnoreClosedness: true, |
| } |
| |
| // API is subsumption used for APIs. |
| var API = Profile{ |
| IgnoreClosedness: true, |
| } |
| |
| // Value subsumes two values based on their logical (evaluated) values. |
| func Value(ctx *adt.OpContext, a, b adt.Value) errors.Error { |
| return CUE.Value(ctx, a, b) |
| } |
| |
| func (p *Profile) Value(ctx *adt.OpContext, a, b adt.Value) errors.Error { |
| s := subsumer{ctx: ctx, Profile: *p} |
| if !s.values(a, b) { |
| return s.getError() |
| } |
| return nil // ignore errors here even if there are some. |
| } |
| |
| // Check reports whether b is an instance of a. |
| func (p *Profile) Check(ctx *adt.OpContext, a, b adt.Value) bool { |
| s := subsumer{ctx: ctx, Profile: *p} |
| return s.values(a, b) |
| } |
| |
| func isBottom(x adt.Node) bool { |
| b, _ := x.(*adt.Bottom) |
| return b != nil |
| } |
| |
| type subsumer struct { |
| ctx *adt.OpContext |
| errs errors.Error |
| |
| Profile |
| |
| inexact bool // If true, the result could be a false negative. |
| missing adt.Feature |
| gt adt.Value |
| lt adt.Value |
| } |
| |
| func (s *subsumer) errf(msg string, args ...interface{}) { |
| b := s.ctx.NewErrf(msg, args...) |
| s.errs = errors.Append(s.errs, b.Err) |
| } |
| |
| func unifyValue(c *adt.OpContext, a, b adt.Value) adt.Value { |
| v := &adt.Vertex{} |
| v.AddConjunct(adt.MakeRootConjunct(c.Env(0), a)) |
| v.AddConjunct(adt.MakeRootConjunct(c.Env(0), b)) |
| x, _ := c.Evaluate(c.Env(0), v) |
| return x |
| } |
| |
| func (s *subsumer) getError() (err errors.Error) { |
| c := s.ctx |
| // src := binSrc(token.NoPos, opUnify, gt, lt) |
| if s.gt != nil && s.lt != nil { |
| // src := binSrc(token.NoPos, opUnify, s.gt, s.lt) |
| if s.missing != 0 { |
| s.errf("missing field %q", s.missing.SelectorString(c)) |
| } else if b, ok := unifyValue(c, s.gt, s.lt).(*adt.Bottom); !ok { |
| s.errf("value not an instance") |
| } else { |
| s.errs = errors.Append(s.errs, b.Err) |
| } |
| } |
| if s.errs == nil { |
| s.errf("value not an instance") |
| } |
| err = s.errs |
| if s.inexact { |
| err = internal.DecorateError(internal.ErrInexact, err) |
| } |
| return err |
| } |