blob: 9722fb1706fe395e7e5df109c73c8b266c6a0d93 [file] [log] [blame]
// Copyright 2018 The 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 cue
import (
"bytes"
)
type valueSubsumer interface {
subsumesImpl(ctx *context, v value) bool
}
type subsumeMode int
const (
// subChoose ensures values are elected before doing a subsumption. This
// feature is on the conservative side and may result in false negatives.
subChoose subsumeMode = 1 << iota
)
// subsumes checks gt subsumes lt. If any of the values contains references or
// unevaluated expressions, structural subsumption is performed. This means
// subsumption is conservative; it may return false when a guarantee for
// subsumption could be proven. For concreted values it returns the exact
// relation. It never returns a false positive.
func subsumes(ctx *context, gt, lt value, mode subsumeMode) bool {
var v, w value
if mode&subChoose == 0 {
v = gt.evalPartial(ctx)
w = lt.evalPartial(ctx)
} else {
v = ctx.manifest(gt)
w = ctx.manifest(lt)
}
if !isIncomplete(v) && !isIncomplete(w) {
gt = v
lt = w
}
a := gt.kind()
b := lt.kind()
switch {
case b == bottomKind:
return true
case b&^(a&b) != 0:
// a does not have strictly more bits. This implies any ground kind
// subsuming a non-ground type.
return false
// TODO: consider not supporting references.
// case (a|b)&(referenceKind) != 0:
// // no resolution if references are in play.
// return false, false
}
return gt.subsumesImpl(ctx, lt, mode)
}
func (x *structLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if o, ok := v.(*structLit); ok {
// all arcs in n must exist in v and its values must subsume.
for _, a := range x.arcs {
b, _ := o.lookup(ctx, a.feature)
if b == nil || !subsumes(ctx, a.v, b, mode) {
return false
}
}
}
return !isBottom(v)
}
func (*top) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
return true
}
func (x *bottom) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
// never called.
return v.kind() == bottomKind
}
func (x *basicType) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
return true
}
func (x *rangeLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
k := unifyType(x.from.kind(), x.to.kind())
if k.isDone() && k.isGround() {
switch y := v.(type) {
case *rangeLit:
// structural equivalence
return subsumes(ctx, x.from, y.from, 0) && subsumes(ctx, x.to, y.to, 0)
case *numLit, *stringLit, *durationLit:
kv := v.kind()
k, _ := matchBinOpKind(opAdd, k, kv)
if k != kv {
return false
}
v := v.(evaluated)
if x.from != nil {
from := minNumRaw(x.from)
if !from.kind().isGround() {
return subsumes(ctx, from, v, 0) // false negative is okay
}
return leq(ctx, x, x.from.(evaluated), v)
}
if x.to != nil {
to := minNumRaw(x.to)
if !to.kind().isGround() {
return subsumes(ctx, to, v, 0) // false negative is okay
}
return leq(ctx, x, v, x.to.(evaluated))
}
return true
}
}
return isBottom(v)
}
func (x *nullLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
return true
}
func (x *boolLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
return x.b == v.(*boolLit).b
}
func (x *stringLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
return x.str == v.(*stringLit).str
}
func (x *bytesLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
return bytes.Equal(x.b, v.(*bytesLit).b)
}
func (x *numLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
b := v.(*numLit)
return x.v.Cmp(&b.v) == 0
}
func (x *durationLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
return x.d == v.(*durationLit).d
}
func (x *list) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
switch y := v.(type) {
case *list:
if !subsumes(ctx, x.len, y.len, mode) {
return false
}
n := len(x.a)
if len(y.a) < n {
n = len(y.a)
}
for i, a := range x.a[:n] {
if !subsumes(ctx, a, y.a[i], mode) {
return false
}
}
if y.isOpen() {
return subsumes(ctx, x.typ, y.typ, 0)
}
for i := range y.a[n:] {
if !subsumes(ctx, x.typ, y.a[i], mode) {
return false
}
}
return true
}
return isBottom(v)
}
func (x *params) subsumes(ctx *context, y *params, mode subsumeMode) bool {
// structural equivalence
// TODO: make agnostic to argument names.
if len(y.arcs) != len(x.arcs) {
return false
}
for i, a := range x.arcs {
if !subsumes(ctx, a.v, y.arcs[i].v, 0) {
return false
}
}
return true
}
func (x *lambdaExpr) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
// structural equivalence
if y, ok := v.(*lambdaExpr); ok {
return x.params.subsumes(ctx, y.params, 0) &&
subsumes(ctx, x.value, y.value, 0)
}
return isBottom(v)
}
// subsumes for disjunction is logically precise. However, just like with
// structural subsumption, it should not have to be called after evaluation.
func (x *disjunction) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if d, ok := v.(*disjunction); ok {
// TODO: the result of subsuming a value by a disjunction is logically
// the subsumed value. However, since we have default semantics, we
// should mark a resulting subsumed value as ambiguous if necessary.
// Also, prove that the returned subsumed single value is always the
// left-most matching value.
return false
// at least one value in x should subsume each value in d.
for _, vd := range d.values {
if !subsumes(ctx, x, vd.val, 0) {
return false
}
}
return true
}
// v is subsumed if any value in x subsumes v.
for _, vx := range x.values {
if subsumes(ctx, vx.val, v, 0) {
return true
}
}
return false
}
// Structural subsumption operations. Should never have to be called after
// evaluation.
// structural equivalence
func (x *nodeRef) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if r, ok := v.(*nodeRef); ok {
return x.node == r.node
}
return isBottom(v)
}
// structural equivalence
func (x *selectorExpr) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if r, ok := v.(*selectorExpr); ok {
return x.feature == r.feature && subsumes(ctx, x.x, r.x, subChoose)
}
return isBottom(v)
}
func (x *interpolation) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
switch v := v.(type) {
case *stringLit:
// Be conservative if not ground.
return false
case *interpolation:
// structural equivalence
if len(x.parts) != len(v.parts) {
return false
}
for i, p := range x.parts {
if !subsumes(ctx, p, v.parts[i], 0) {
return false
}
}
return true
}
return false
}
// structural equivalence
func (x *indexExpr) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
// TODO: what does it mean to subsume if the index value is not known?
if r, ok := v.(*indexExpr); ok {
// TODO: could be narrowed down if we know the exact value of the index
// and referenced value.
return subsumes(ctx, x.x, r.x, mode) && subsumes(ctx, x.index, r.index, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *sliceExpr) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
// TODO: what does it mean to subsume if the index value is not known?
if r, ok := v.(*sliceExpr); ok {
// TODO: could be narrowed down if we know the exact value of the index
// and referenced value.
return subsumes(ctx, x.x, r.x, 0) &&
subsumes(ctx, x.lo, r.lo, 0) &&
subsumes(ctx, x.hi, r.hi, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *callExpr) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if c, ok := v.(*callExpr); ok {
if len(x.args) != len(c.args) {
return false
}
for i, a := range x.args {
if !subsumes(ctx, a, c.args[i], 0) {
return false
}
}
return subsumes(ctx, x.x, c.x, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *unaryExpr) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if b, ok := v.(*unaryExpr); ok {
return x.op == b.op && subsumes(ctx, x.x, b.x, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *binaryExpr) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if b, ok := v.(*binaryExpr); ok {
return x.op == b.op &&
subsumes(ctx, x.left, b.left, 0) &&
subsumes(ctx, x.right, b.right, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *listComprehension) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if b, ok := v.(*listComprehension); ok {
return subsumes(ctx, x.clauses, b.clauses, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *structComprehension) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if b, ok := v.(*structComprehension); ok {
return subsumes(ctx, x.clauses, b.clauses, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *yield) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if b, ok := v.(*yield); ok {
return subsumes(ctx, x.key, b.key, 0) &&
subsumes(ctx, x.value, b.value, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *feed) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if b, ok := v.(*feed); ok {
return subsumes(ctx, x.source, b.source, 0) &&
subsumes(ctx, x.fn, b.fn, 0)
}
return isBottom(v)
}
// structural equivalence
func (x *guard) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
if b, ok := v.(*guard); ok {
return subsumes(ctx, x.condition, b.condition, 0) &&
subsumes(ctx, x.value, b.value, 0)
}
return isBottom(v)
}