cuelang.org/go@v0.10.1/internal/core/adt/tasks.go (about)

     1  // Copyright 2023 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  	"fmt"
    19  
    20  	"cuelang.org/go/cue/ast"
    21  	"cuelang.org/go/cue/token"
    22  )
    23  
    24  var (
    25  	handleExpr              *runner
    26  	handleResolver          *runner
    27  	handleDynamic           *runner
    28  	handlePatternConstraint *runner
    29  	handleComprehension     *runner
    30  	handleListLit           *runner
    31  	handleListVertex        *runner
    32  	handleDisjunctions      *runner
    33  )
    34  
    35  // Use init to avoid a (spurious?) cyclic dependency in Go.
    36  func init() {
    37  	handleExpr = &runner{
    38  		name:      "Expr",
    39  		f:         processExpr,
    40  		completes: genericConjunct,
    41  	}
    42  	handleResolver = &runner{
    43  		name:      "Resolver",
    44  		f:         processResolver,
    45  		completes: genericConjunct,
    46  	}
    47  	handleDynamic = &runner{
    48  		name:      "Dynamic",
    49  		f:         processDynamic,
    50  		completes: fieldConjunct,
    51  	}
    52  	handlePatternConstraint = &runner{
    53  		name:      "PatternConstraint",
    54  		f:         processPatternConstraint,
    55  		completes: allTasksCompleted | fieldConjunctsKnown,
    56  	}
    57  	handleComprehension = &runner{
    58  		name:      "Comprehension",
    59  		f:         processComprehension,
    60  		completes: valueKnown | allTasksCompleted | fieldConjunctsKnown,
    61  	}
    62  	handleListLit = &runner{
    63  		name:      "ListLit",
    64  		f:         processListLit,
    65  		completes: fieldConjunct,
    66  		needs:     listTypeKnown,
    67  	}
    68  	handleListVertex = &runner{
    69  		name:      "ListVertex",
    70  		f:         processListVertex,
    71  		completes: fieldConjunct,
    72  		needs:     listTypeKnown,
    73  	}
    74  	handleDisjunctions = &runner{
    75  		name:      "Disjunctions",
    76  		f:         processDisjunctions,
    77  		completes: genericDisjunction,
    78  		priority:  1,
    79  	}
    80  }
    81  
    82  // This file contains task runners (func(ctx *OpContext, t *task, mode runMode)).
    83  
    84  func processExpr(ctx *OpContext, t *task, mode runMode) {
    85  	x := t.x.(Expr)
    86  
    87  	state := combineMode(concreteKnown, mode)
    88  	v := ctx.evalState(x, state)
    89  	t.node.insertValueConjunct(t.env, v, t.id)
    90  }
    91  
    92  func processResolver(ctx *OpContext, t *task, mode runMode) {
    93  	r := t.x.(Resolver)
    94  
    95  	// TODO(perf): if we are resolving a value where we know a scalar value can
    96  	// be conclusive, we could avoid triggering evaluating disjunctions. This
    97  	// would be a pretty significant rework, though.
    98  
    99  	arc := r.resolve(ctx, oldOnly(0))
   100  	if arc == nil {
   101  		// TODO: yield instead?
   102  		return
   103  	}
   104  	arc = arc.DerefNonDisjunct()
   105  
   106  	ctx.Logf(t.node.node, "RESOLVED %v to %v %v", r, arc.Label, fmt.Sprintf("%p", arc))
   107  	// TODO: consider moving after markCycle or removing.
   108  	d := arc.DerefDisjunct()
   109  
   110  	// A reference that points to itself indicates equality. In that case
   111  	// we are done computing and we can return the arc as is.
   112  	ci, skip := t.node.markCycle(d, t.env, r, t.id)
   113  	if skip {
   114  		return
   115  	}
   116  
   117  	if t.defunct {
   118  		return
   119  	}
   120  
   121  	c := MakeConjunct(t.env, t.x, ci)
   122  	t.node.scheduleVertexConjuncts(c, arc, ci)
   123  }
   124  
   125  func processDynamic(ctx *OpContext, t *task, mode runMode) {
   126  	n := t.node
   127  
   128  	field := t.x.(*DynamicField)
   129  
   130  	v := ctx.scalarValue(t, field.Key)
   131  	if v == nil {
   132  		return
   133  	}
   134  
   135  	if v.Concreteness() != Concrete {
   136  		n.addBottom(&Bottom{
   137  			Code: IncompleteError,
   138  			Err: ctx.NewPosf(pos(field.Key),
   139  				"key value of dynamic field must be concrete, found %v", v),
   140  		})
   141  		return
   142  	}
   143  
   144  	f := ctx.Label(field.Key, v)
   145  	// TODO: remove this restriction.
   146  	if f.IsInt() {
   147  		n.addErr(ctx.NewPosf(pos(field.Key), "integer fields not supported"))
   148  		return
   149  	}
   150  
   151  	c := MakeConjunct(t.env, field, t.id)
   152  	c.CloseInfo.cc = nil
   153  	n.insertArc(f, field.ArcType, c, t.id, true)
   154  }
   155  
   156  func processPatternConstraint(ctx *OpContext, t *task, mode runMode) {
   157  	n := t.node
   158  
   159  	field := t.x.(*BulkOptionalField)
   160  
   161  	// Note that the result may be a disjunction. Be sure to not take the
   162  	// default value as we want to retain the options of the disjunction.
   163  	v := ctx.evalState(field.Filter, require(0, scalarKnown))
   164  	if v == nil {
   165  		return
   166  	}
   167  
   168  	n.insertPattern(v, MakeConjunct(t.env, t.x, t.id))
   169  }
   170  
   171  func processComprehension(ctx *OpContext, t *task, mode runMode) {
   172  	n := t.node
   173  
   174  	y := &envYield{
   175  		envComprehension: t.comp,
   176  		leaf:             t.leaf,
   177  		env:              t.env,
   178  		id:               t.id,
   179  		expr:             t.x,
   180  	}
   181  
   182  	err := n.processComprehension(y, 0)
   183  	t.err = CombineErrors(nil, t.err, err)
   184  	t.comp.vertex.state.addBottom(err)
   185  }
   186  
   187  func processDisjunctions(c *OpContext, t *task, mode runMode) {
   188  	n := t.node
   189  	err := n.processDisjunctions()
   190  	t.err = CombineErrors(nil, t.err, err)
   191  }
   192  
   193  func processFinalizeDisjunctions(c *OpContext, t *task, mode runMode) {
   194  	n := t.node
   195  	n.finalizeDisjunctions()
   196  }
   197  
   198  func processListLit(c *OpContext, t *task, mode runMode) {
   199  	n := t.node
   200  
   201  	l := t.x.(*ListLit)
   202  
   203  	n.updateCyclicStatus(t.id)
   204  
   205  	var ellipsis Node
   206  
   207  	index := int64(0)
   208  	hasComprehension := false
   209  	for j, elem := range l.Elems {
   210  		// TODO: Terminate early in case of runaway comprehension.
   211  
   212  		switch x := elem.(type) {
   213  		case *Comprehension:
   214  			err := c.yield(nil, t.env, x, 0, func(e *Environment) {
   215  				label, err := MakeLabel(x.Source(), index, IntLabel)
   216  				n.addErr(err)
   217  				index++
   218  				c := MakeConjunct(e, x.Value, t.id)
   219  				n.insertArc(label, ArcMember, c, t.id, true)
   220  			})
   221  			hasComprehension = true
   222  			if err != nil {
   223  				n.addBottom(err)
   224  				return
   225  			}
   226  
   227  		case *Ellipsis:
   228  			if j != len(l.Elems)-1 {
   229  				n.addErr(c.Newf("ellipsis must be last element in list"))
   230  				return
   231  			}
   232  
   233  			elem := x.Value
   234  			if elem == nil {
   235  				elem = &Top{}
   236  			}
   237  
   238  			c := MakeConjunct(t.env, elem, t.id)
   239  			pat := &BoundValue{
   240  				Op:    GreaterEqualOp,
   241  				Value: n.ctx.NewInt64(index, x),
   242  			}
   243  			n.insertPattern(pat, c)
   244  			ellipsis = x
   245  
   246  		default:
   247  			label, err := MakeLabel(x.Source(), index, IntLabel)
   248  			n.addErr(err)
   249  			index++
   250  			c := MakeConjunct(t.env, x, t.id)
   251  			n.insertArc(label, ArcMember, c, t.id, true)
   252  		}
   253  
   254  		if max := n.maxListLen; n.listIsClosed && int(index) > max {
   255  			n.invalidListLength(max, len(l.Elems), n.maxNode, l)
   256  			return
   257  		}
   258  	}
   259  
   260  	isClosed := ellipsis == nil
   261  
   262  	switch max := n.maxListLen; {
   263  	case int(index) < max:
   264  		if isClosed {
   265  			n.invalidListLength(int(index), max, l, n.maxNode)
   266  			return
   267  		}
   268  
   269  	case int(index) > max,
   270  		isClosed && !n.listIsClosed,
   271  		(isClosed == n.listIsClosed) && !hasComprehension:
   272  		n.maxListLen = int(index)
   273  		n.maxNode = l
   274  		n.listIsClosed = isClosed
   275  	}
   276  
   277  	n.updateListType(l, t.id, isClosed, ellipsis)
   278  }
   279  
   280  func processListVertex(c *OpContext, t *task, mode runMode) {
   281  	n := t.node
   282  
   283  	l := t.x.(*Vertex)
   284  
   285  	elems := l.Elems()
   286  	isClosed := l.IsClosedList()
   287  
   288  	// TODO: Share with code above.
   289  	switch max := n.maxListLen; {
   290  	case len(elems) < max:
   291  		if isClosed {
   292  			n.invalidListLength(len(elems), max, l, n.maxNode)
   293  			return
   294  		}
   295  
   296  	case len(elems) > max:
   297  		if n.listIsClosed {
   298  			n.invalidListLength(max, len(elems), n.maxNode, l)
   299  			return
   300  		}
   301  		n.listIsClosed = isClosed
   302  		n.maxListLen = len(elems)
   303  		n.maxNode = l
   304  
   305  	case isClosed:
   306  		n.listIsClosed = true
   307  		n.maxNode = l
   308  	}
   309  
   310  	for _, a := range elems {
   311  		if a.Conjuncts == nil {
   312  			c := MakeRootConjunct(nil, a)
   313  			n.insertArc(a.Label, ArcMember, c, CloseInfo{}, true)
   314  			continue
   315  		}
   316  		for _, c := range a.Conjuncts {
   317  			c.CloseInfo.cc = t.id.cc
   318  			n.insertArc(a.Label, ArcMember, c, t.id, true)
   319  		}
   320  	}
   321  
   322  	n.updateListType(l, t.id, isClosed, nil)
   323  }
   324  
   325  func (n *nodeContext) updateListType(list Expr, id CloseInfo, isClosed bool, ellipsis Node) {
   326  	if n.kind == 0 {
   327  		n.node.updateStatus(finalized) // TODO(neweval): remove once transitioned.
   328  		return
   329  	}
   330  	m, ok := n.node.BaseValue.(*ListMarker)
   331  	if !ok {
   332  		m = &ListMarker{
   333  			IsOpen: true,
   334  		}
   335  		n.node.setValue(n.ctx, conjuncts, m)
   336  	}
   337  	m.IsOpen = m.IsOpen && !isClosed
   338  
   339  	if ellipsis != nil {
   340  		if src, _ := ellipsis.Source().(ast.Expr); src != nil {
   341  			if m.Src == nil {
   342  				m.Src = src
   343  			} else {
   344  				m.Src = ast.NewBinExpr(token.AND, m.Src, src)
   345  			}
   346  		}
   347  	}
   348  
   349  	if n.kind != ListKind {
   350  		n.updateNodeType(ListKind, list, id)
   351  	}
   352  }