blob: 3491dfa41169bbbe714de3adc78f7b1e526fceb0 [file] [log] [blame]
// 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 eval
// The file implements the majority of the closed struct semantics. The data is
// recorded in the Closed field of a Vertex.
//
// Each vertex has a set of conjuncts that make up the values of the vertex.
// Each Conjunct may originate from various sources, like an embedding, field
// definition or regular value. For the purpose of computing the value, the
// source of the conjunct is irrelevant. The origin does matter, however, for
// determining whether a field is allowed in a closed struct. The Closed field
// keeps track of the kind of origin for this purpose.
//
// More precisely, the CloseDef struct explains how the conjuncts of an arc were
// combined for instance due to a conjunction with closed struct or through an
// embedding. Each Vertex may be associated with a slice of CloseDefs. The
// position of a CloseDef in a file corresponds to an adt.ID.
//
// While evaluating each conjunct, new CloseDefs are added to indicate how a
// conjunct relates to its parent as needed. For instance, if a field references
// a definition, all other previous checks are useless, as the newly referred to
// definitions define an upper bound and will contain all the information that
// is necessary to determine whether a field may be included.
//
// Most of the logic in this file concerns itself with the combination of
// multiple CloseDef values as well as traversing the structure to validate
// whether an arc is allowed. The actual fieldSet logic is in optional.go The
// overall control and use of the functionality in this file is used in eval.go.
import (
"fmt"
"cuelang.org/go/internal/core/adt"
)
// acceptor implements adt.Acceptor.
//
// Note that it keeps track of whether it represents a closed struct. An
// acceptor is also used to associate an CloseDef with a Vertex, and not
// all CloseDefs represent a closed struct: a value that contains embeddings may
// eventually turn into a closed struct. Consider
//
// a: {
// b
// d: e: int
// }
// b: d: {
// #A & #B
// }
//
// At the point of evaluating `a`, the struct is not yet closed. However,
// descending into `d` will trigger the inclusion of definitions which in turn
// causes the struct to be closed. At this point, it is important to know that
// `b` originated from an embedding, as otherwise `e` may not be allowed.
//
type acceptor struct {
Canopy []CloseDef
Fields []*fieldSet
// TODO: isClosed could be removed if we can include closedness fully
// in the ClosedDefs representing conjuncts. This, in turn, will allow
// various optimizations, like having to record field sets for data
// vertices.
isClosed bool
isList bool
openList bool
// ignore tells to the closedness check for one level. This allows
// constructed parent nodes that do not have field sets defined.
ignore bool
}
func (a *acceptor) clone() *acceptor {
canopy := make([]CloseDef, len(a.Canopy))
copy(canopy, a.Canopy)
for i := range canopy {
canopy[i].IsClosed = false
}
return &acceptor{
Canopy: canopy,
isClosed: a.isClosed,
}
}
func (a *acceptor) Accept(c *adt.OpContext, f adt.Feature) bool {
if a.isList {
return a.openList
}
// TODO: remove these two checks and always pass InvalidLabel.
if !a.isClosed {
return true
}
if f == adt.InvalidLabel {
return false
}
if f.IsInt() {
return a.openList
}
return a.verifyArcAllowed(c, f, nil)
}
func (a *acceptor) MatchAndInsert(c *adt.OpContext, v *adt.Vertex) {
a.visitAllFieldSets(func(fs *fieldSet) {
fs.MatchAndInsert(c, v)
})
}
func (a *acceptor) OptionalTypes() (mask adt.OptionalType) {
a.visitAllFieldSets(func(f *fieldSet) {
mask |= f.OptionalTypes()
})
return mask
}
// A disjunction acceptor represents a disjunction of all possible fields. Note
// that this is never used in evaluation as evaluation stops at incomplete nodes
// and a disjunction is incomplete. When the node is referenced, the original
// conjuncts are used instead.
//
// The value may be used in the API, though, where it may be an argument to
// UnifyAccept.
//
// TODO(perf): it would be sufficient to only implement the Accept method of an
// Acceptor. This could be implemented as an allocation-free wrapper type around
// a Disjunction. This will require a bit more API cleaning, though.
func newDisjunctionAcceptor(x *adt.Disjunction) adt.Acceptor {
n := &acceptor{}
for _, d := range x.Values {
if a, _ := d.Closed.(*acceptor); a != nil {
offset := n.InsertSubtree(0, nil, d, false)
a.visitAllFieldSets(func(f *fieldSet) {
g := *f
g.id += offset
n.insertFieldSet(g.id, &g)
})
}
}
return n
}
// CloseDef defines how individual fieldSets (corresponding to conjuncts)
// combine to determine whether a field is contained in a closed set.
//
// A CloseDef combines multiple conjuncts and embeddings. All CloseDefs are
// stored in slice. References to other CloseDefs are indices within this slice.
// Together they define the top of the tree of the expression tree of how
// conjuncts combine together (a canopy).
type CloseDef struct {
Src adt.Node
// And is used to track the IDs of a set of conjuncts. If IsDef or IsClosed
// is true, a field is only allowed if at least one of the corresponding
// fieldsets associated with this node or its embeddings allows it.
//
// And nodes are linked in a ring, meaning that the last node points back
// to the first node. This allows a traversal of all and nodes to commence
// at any point in the ring.
And adt.ID
// NextEmbed indicates the first ID for a linked list of embedded
// expressions. The node corresponding to the actual embedding is at
// position NextEmbed+1. The linked-list nodes all have a value of -1 for
// And. NextEmbed is 0 for the last element in the list.
NextEmbed adt.ID
// IsDef indicates this node is associated with a definition and that all
// expressions are recursively closed. This value is "sticky" when a child
// node copies the closedness data from a parent node.
IsDef bool
// IsClosed indicates this node is associated with the result of close().
// A child vertex should not "inherit" this value.
IsClosed bool
}
func (n *CloseDef) isRequired() bool {
return n.IsDef || n.IsClosed
}
const embedRoot adt.ID = -1
type Entry = fieldSet
// TODO: this may be an idea to get rid of acceptor.isClosed. There
// are various more things to consider, though.
//
// func (c *acceptor) isRequired(id adt.ID) bool {
// req := false
// c.visitAnd(id, func(id adt.ID, n CloseDef) bool {
// if n.isRequired() {
// req = true
// return false
// }
// c.visitEmbed(id, func(id adt.ID, n CloseDef) bool {
// if n.isRequired() {
// req = true
// return false
// }
// req = req || c.isRequired(id)
// return true
// })
// return true
// })
// return req
// }
func (c *acceptor) visitAllFieldSets(f func(f *fieldSet)) {
for _, set := range c.Fields {
for ; set != nil; set = set.next {
f(set)
}
}
}
func (c *acceptor) visitAnd(id adt.ID, f func(id adt.ID, n CloseDef) bool) bool {
for i := id; ; {
x := c.Canopy[i]
if !f(i, x) {
return false
}
if i = x.And; i == id {
break
}
}
return true
}
func (c *acceptor) visitOr(id adt.ID, f func(id adt.ID, n CloseDef) bool) bool {
if !f(id, c.Canopy[id]) {
return false
}
return c.visitEmbed(id, f)
}
func (c *acceptor) visitEmbed(id adt.ID, f func(id adt.ID, n CloseDef) bool) bool {
for i := c.Canopy[id].NextEmbed; i != 0; i = c.Canopy[i].NextEmbed {
if id := i + 1; !f(id, c.Canopy[id]) {
return false
}
}
return true
}
func (c *acceptor) node(id adt.ID) *CloseDef {
if len(c.Canopy) == 0 {
c.Canopy = append(c.Canopy, CloseDef{})
}
return &c.Canopy[id]
}
func (c *acceptor) fieldSet(at adt.ID) *fieldSet {
if int(at) >= len(c.Fields) {
return nil
}
return c.Fields[at]
}
func (c *acceptor) insertFieldSet(at adt.ID, e *fieldSet) {
c.node(0) // Ensure the canopy is at least length 1.
if len(c.Fields) < len(c.Canopy) {
a := make([]*fieldSet, len(c.Canopy))
copy(a, c.Fields)
c.Fields = a
}
e.next = c.Fields[at]
c.Fields[at] = e
}
// InsertDefinition appends a new CloseDef to Canopy representing a reference to
// a definition at the given position. It returns the position of the new
// CloseDef.
func (c *acceptor) InsertDefinition(at adt.ID, src adt.Node) (id adt.ID) {
if len(c.Canopy) == 0 {
c.Canopy = append(c.Canopy, CloseDef{})
}
if int(at) >= len(c.Canopy) {
panic(fmt.Sprintf("at >= len(canopy) (%d >= %d)", at, len(c.Canopy)))
}
// New there is a new definition, the parent location (invariant) is no
// longer a required entry and could be dropped if there were no more
// fields.
// #orig: #d // only fields in #d are sufficient to check.
// #orig: {a: b}
c.Canopy[at].IsDef = false
id = adt.ID(len(c.Canopy))
y := CloseDef{
Src: src,
And: c.Canopy[at].And,
NextEmbed: 0,
IsDef: true,
}
c.Canopy[at].And = id
c.Canopy = append(c.Canopy, y)
return id
}
// InsertEmbed appends a new CloseDef to Canopy representing the use of an
// embedding at the given position. It returns the position of the new CloseDef.
func (c *acceptor) InsertEmbed(at adt.ID, src adt.Node) (id adt.ID) {
if len(c.Canopy) == 0 {
c.Canopy = append(c.Canopy, CloseDef{})
}
if int(at) >= len(c.Canopy) {
panic(fmt.Sprintf("at >= len(canopy) (%d >= %d)", at, len(c.Canopy)))
}
id = adt.ID(len(c.Canopy))
y := CloseDef{
And: -1,
NextEmbed: c.Canopy[at].NextEmbed,
}
z := CloseDef{Src: src, And: id + 1}
c.Canopy[at].NextEmbed = id
c.Canopy = append(c.Canopy, y, z)
return id + 1
}
// isComplexStruct reports whether the Closed information should be copied as a
// subtree into the parent node using InsertSubtree. If not, the conjuncts can
// just be inserted at the current ID.
func isComplexStruct(v *adt.Vertex) bool {
m, _ := v.Value.(*adt.StructMarker)
if m == nil {
return false
}
a, _ := v.Closed.(*acceptor)
if a == nil {
return false
}
if a.isClosed {
return true
}
switch len(a.Canopy) {
case 0:
return false
case 1:
// TODO: should we check for closedness?
x := a.Canopy[0]
return x.isRequired()
}
return true
}
// InsertSubtree inserts the closedness information of v into c as an embedding
// at the current position and inserts conjuncts of v into n (if not nil). It
// inserts it as an embedding and not and to cover either case. The idea is that
// one of the values were supposed to be closed, a separate node entry would
// already have been created.
//
// TODO: get rid of this. This is now only used in newDisjunctionAcceptor,
// which, in turn, is rarely used for analyzing disjunction values in the API.
// This code is not ideal and buggy (see comment below), but it doesn't seem
// worth improving it and we can probably do without.
func (c *acceptor) InsertSubtree(at adt.ID, n *nodeContext, v *adt.Vertex, cyclic bool) adt.ID {
if len(c.Canopy) == 0 {
c.Canopy = append(c.Canopy, CloseDef{})
}
if int(at) >= len(c.Canopy) {
panic(fmt.Sprintf("at >= len(canopy) (%d >= %d)", at, len(c.Canopy)))
}
// TODO: like with AddVertex, this really should use the acceptor of the
// parent. This seems not to work, though.
//
// var a *acceptor
// if v.Parent != nil && v.Parent.Closed != nil {
// a = closedInfo(v.Parent)
// } else {
// a = &acceptor{}
// }
a := closedInfo(v)
a.node(0)
id := adt.ID(len(c.Canopy))
y := CloseDef{
And: embedRoot,
NextEmbed: c.Canopy[at].NextEmbed,
}
c.Canopy[at].NextEmbed = id
c.Canopy = append(c.Canopy, y)
id = adt.ID(len(c.Canopy))
// First entry is at the embedded node location.
c.Canopy = append(c.Canopy, a.Canopy...)
// Shift all IDs for the new offset.
for i := int(id); i < len(c.Canopy); i++ {
x := c.Canopy[i]
if x.And != -1 {
c.Canopy[i].And += id
}
if x.NextEmbed != 0 {
c.Canopy[i].NextEmbed += id
}
}
if n != nil {
for _, c := range v.Conjuncts {
c = updateCyclic(c, cyclic, nil, nil)
c.CloseID += id
n.addExprConjunct(c)
}
}
return id
}
func appendConjuncts(v *adt.Vertex, a []adt.Conjunct) {
for _, c := range a {
v.AddConjunct(c)
}
}
// AddVertex add a Vertex to a new destination node. The caller may
// call AddVertex multiple times on dst. None of the fields of dst
// should be set by the caller. AddVertex takes care of setting the
// Label and Parent.
func AddVertex(dst, src *adt.Vertex) {
if dst.Parent == nil {
// Create "fake" parent that holds the combined closed data.
// We do not set the parent until here as we don't want to "inherit" the
// closedness setting from v.
dst.Parent = &adt.Vertex{Parent: src.Parent}
dst.Label = src.Label
}
if src.IsData() {
dst.AddConjunct(adt.MakeConjunct(nil, src, 0))
return
}
var srcC *acceptor
if src.Parent != nil && src.Parent.Closed != nil {
srcC = closedInfo(src.Parent)
} else {
srcC = &acceptor{}
}
dstC := closedInfo(dst.Parent)
dstC.ignore = true
isDef := src.Label.IsDef()
addClose := isDef || srcC.isClosed
if addClose {
dstC.node(0)
} else if len(dstC.Canopy) == 0 {
// we can copy it as is (assuming 0 is )
appendConjuncts(dst, src.Conjuncts)
dstC.Canopy = append(dstC.Canopy, srcC.Canopy...)
return
}
offset := adt.ID(len(dstC.Canopy))
top := offset
switch {
case len(srcC.Canopy) == 0 && !addClose:
appendConjuncts(dst, src.Conjuncts)
return
case len(srcC.Canopy) == 1:
c := srcC.Canopy[0]
if !addClose && !c.IsDef && !c.IsClosed {
appendConjuncts(dst, src.Conjuncts)
return
}
c.IsDef = addClose
dstC.Canopy = append(dstC.Canopy, c)
case addClose:
srcC.node(0)
isClosed := false
srcC.visitAnd(0, func(id adt.ID, c CloseDef) bool {
if c.IsClosed || c.IsDef {
isClosed = true
return false
}
return true
})
if !isClosed {
// need to embed and close.
dstC.Canopy = append(dstC.Canopy,
CloseDef{
And: offset,
IsDef: true,
NextEmbed: offset + 1,
},
CloseDef{
And: embedRoot,
})
offset += 2
}
fallthrough
default:
dstC.Canopy = append(dstC.Canopy, srcC.Canopy...)
}
// Shift all IDs for the new offset.
for i := int(offset); i < len(dstC.Canopy); i++ {
x := dstC.Canopy[i]
if x.And != -1 {
dstC.Canopy[i].And += offset
}
if x.NextEmbed != 0 {
dstC.Canopy[i].NextEmbed += offset
}
}
topAnd := dstC.Canopy[top].And
atAnd := dstC.Canopy[0].And
dstC.Canopy[top].And = atAnd
dstC.Canopy[0].And = topAnd
for _, c := range src.Conjuncts {
c.CloseID += offset
if err := dst.AddConjunct(c); err != nil {
panic(err)
}
}
}
// TODO: This is roughly the equivalent of AddVertex for use with
// UnifyAccept. It is based on the old InsertSubTree. We should use
// this at some point, but ideally we should have a better way to
// represent closedness in the first place that is more flexible in
// handling API usage.
//
// func EmbedVertex(dst, src *adt.Vertex) {
// if dst.Parent == nil {
// // Create "fake" parent that holds the combined closed data.
// // We do not set the parent until here as we don't want to "inherit" the
// // closedness setting from v.
// dst.Parent = &adt.Vertex{Parent: src.Parent}
// dst.Label = src.Label
// }
//
// if src.IsData() {
// dst.AddConjunct(adt.MakeConjunct(nil, src, 0))
// return
// }
//
// var a *acceptor
// if src.Parent != nil && src.Parent.Closed != nil {
// a = closedInfo(src.Parent)
// } else {
// a = &acceptor{}
// }
// a.node(0)
//
// c := closedInfo(dst.Parent)
// c.ignore = true
// c.node(0)
//
// id := adt.ID(len(c.Canopy))
// y := CloseDef{
// And: embedRoot,
// NextEmbed: c.Canopy[0].NextEmbed,
// }
// c.Canopy[0].NextEmbed = id
//
// c.Canopy = append(c.Canopy, y)
// id = adt.ID(len(c.Canopy))
//
// // First entry is at the embedded node location.
// c.Canopy = append(c.Canopy, a.Canopy...)
//
// // Shift all IDs for the new offset.
// for i := int(id); i < len(c.Canopy); i++ {
// x := c.Canopy[i]
// if x.And != -1 {
// c.Canopy[i].And += id
// }
// if x.NextEmbed != 0 {
// c.Canopy[i].NextEmbed += id
// }
// }
//
// for _, c := range src.Conjuncts {
// c.CloseID += id
// if err := dst.AddConjunct(c); err != nil {
// panic(err)
// }
// }
// }
func (c *acceptor) verifyArc(ctx *adt.OpContext, f adt.Feature, v *adt.Vertex) (found bool, err *adt.Bottom) {
defer ctx.ReleasePositions(ctx.MarkPositions())
c.node(0) // ensure at least a size of 1.
if c.verify(ctx, f) {
return true, nil
}
// TODO: also disallow non-hidden definitions.
if !f.IsString() && f != adt.InvalidLabel {
return false, nil
}
if v != nil {
for _, c := range v.Conjuncts {
if pos := c.Field(); pos != nil {
ctx.AddPosition(pos)
}
}
}
// collect positions from tree.
for _, c := range c.Canopy {
if c.Src != nil {
ctx.AddPosition(c.Src)
}
}
label := f.SelectorString(ctx)
return false, ctx.NewErrf("field `%s` not allowed", label)
}
func (c *acceptor) verifyArcAllowed(ctx *adt.OpContext, f adt.Feature, v *adt.Vertex) bool {
// TODO: also disallow non-hidden definitions.
if !f.IsString() && f != adt.InvalidLabel {
return true
}
defer ctx.ReleasePositions(ctx.MarkPositions())
c.node(0) // ensure at least a size of 1.
return c.verify(ctx, f)
}
func (c *acceptor) verify(ctx *adt.OpContext, f adt.Feature) bool {
ok, required := c.verifyAnd(ctx, 0, f)
return ok || (!required && !c.isClosed)
}
// verifyAnd reports whether f is contained in all closed conjuncts at id and,
// if not, whether the precense of at least one entry is required.
func (c *acceptor) verifyAnd(ctx *adt.OpContext, id adt.ID, f adt.Feature) (found, required bool) {
for i := id; ; {
x := c.Canopy[i]
if ok, req := c.verifySets(ctx, i, f); ok {
found = true
} else if ok, isClosed := c.verifyEmbed(ctx, i, f); ok {
found = true
} else if req || x.isRequired() {
// Not found for a closed entry so this indicates a failure.
return false, true
} else if isClosed {
// The node itself isn't closed, but an embedding indicates it
// should. See cue/testdata/definitions/embed.txtar.
required = true
}
if i = x.And; i == id {
break
}
}
return found, required
}
// verifyEmbed reports whether any of the embeddings for the node at id allows f
// and, if not, whether the embeddings imply that the enclosing node should be
// closed. The latter is the case when embedded struct itself is closed.
func (c *acceptor) verifyEmbed(ctx *adt.OpContext, id adt.ID, f adt.Feature) (found, isClosed bool) {
for i := c.Canopy[id].NextEmbed; i != 0; i = c.Canopy[i].NextEmbed {
ok, req := c.verifyAnd(ctx, i+1, f)
if ok {
return true, false
}
if req {
isClosed = true
}
}
return false, isClosed
}
func (c *acceptor) verifySets(ctx *adt.OpContext, id adt.ID, f adt.Feature) (found, required bool) {
o := c.fieldSet(id)
if o == nil {
return false, false
}
for isRegular := f.IsRegular(); o != nil; o = o.next {
if isRegular && (len(o.additional) > 0 || o.isOpen) {
return true, false
}
for _, g := range o.fields {
if f == g.label {
return true, false
}
}
if !isRegular {
continue
}
for _, b := range o.bulk {
if b.check.Match(ctx, f) {
return true, false
}
}
}
// TODO: this is the same location where code is registered as the old code,
// but
for o := c.Fields[id]; o != nil; o = o.next {
if o.pos != nil {
ctx.AddPosition(o.pos)
}
}
return false, false
}
type info struct {
referred bool
up adt.ID
replace adt.ID
reverse adt.ID
}
func (c *acceptor) Compact(all []adt.Conjunct) (compacted []CloseDef) {
a := c.Canopy
if len(a) == 0 {
return nil
}
marked := make([]info, len(a))
c.markParents(0, marked)
// Mark all entries that cannot be dropped.
for _, x := range all {
c.markUsed(x.CloseID, marked)
}
// Compute compact numbers and reverse.
k := adt.ID(0)
for i, x := range marked {
if x.referred {
marked[i].replace = k
marked[k].reverse = adt.ID(i)
k++
}
}
compacted = make([]CloseDef, k)
for i := range compacted {
orig := c.Canopy[marked[i].reverse]
and := orig.And
if and != embedRoot {
and = marked[orig.And].replace
}
compacted[i] = CloseDef{
Src: orig.Src,
And: and,
IsDef: orig.IsDef,
}
last := adt.ID(i)
for or := orig.NextEmbed; or != 0; or = c.Canopy[or].NextEmbed {
if marked[or].referred {
compacted[last].NextEmbed = marked[or].replace
last = marked[or].replace
}
}
}
// Update conjuncts
for i, x := range all {
all[i].CloseID = marked[x.ID()].replace
}
return compacted
}
func (c *acceptor) markParents(parent adt.ID, info []info) {
// Ands are arranged in a ring, so check for parent, not 0.
c.visitAnd(parent, func(i adt.ID, x CloseDef) bool {
c.visitEmbed(i, func(j adt.ID, x CloseDef) bool {
info[j-1].up = i
info[j].up = i
c.markParents(j, info)
return true
})
return true
})
}
func (c *acceptor) markUsed(id adt.ID, marked []info) {
if marked[id].referred {
return
}
if id > 0 && c.Canopy[id-1].And == embedRoot {
marked[id-1].referred = true
}
for i := id; !marked[i].referred; i = c.Canopy[i].And {
marked[i].referred = true
}
c.markUsed(marked[id].up, marked)
}