this repo has no description
1// Copyright 2024 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
15package toposort
16
17// Ultimately we need to build a graph of field names. Those field
18// names can come from different constructions, such as:
19//
20// 1. Within a struct
21//
22// x: {z: _, y: _}
23//
24// When considering x, there should be a edge from z to y (written
25// from now on as (z -> y)).
26//
27// 2. Explicit unification
28//
29// x: {z: _, y: _} & {x: _, w: _}
30//
31// When considering x, we want no edges between the arguments of the
32// explicit unification operator '&'. There should only be edges (z
33// -> y) and (x -> w). Through explicit unifications, cycles of field
34// names can be introduced, e.g.:
35//
36// x: {z: _, y: _} & {y: _, w: _, z: _}
37//
38// 3. Embeddings
39//
40// b: {x: _, w: _}
41// a: {z: _, y: _}
42// c: { a, b }
43//
44// Here, a and b are embedded within c, and the order is important, so
45// at a minimum we want edges (z -> y), (x -> w), and (y -> x). Other
46// edges which don't introduce cycles are also acceptable (e.g. (z ->
47// x), (y -> w) etc).
48//
49// 4. Implicit unification
50//
51// c: {z: _, y: _}
52// c: {x: _, w: _}
53//
54// Here, like with embeddings, we choose that the source order is
55// important, and so we must have a minimum of (z -> y), (x -> w) and
56// (y -> x).
57//
58// Currently, the evaluator does not always provide enough information
59// for us to be able to reliably identify all implicit unifications,
60// especially where the ordering is enforced via some intermediate
61// node. For example:
62//
63// a: {
64// d: z: _
65// d: t: _
66// e: {x: _, w: _}
67// }
68// c: a.d & a.e
69//
70// Here, the information we get when sorting the fields of c (post
71// evaluation), is insufficient to be able to establish the edge (z ->
72// t), but it is sufficient to establish (x -> w). So in this case, we
73// end up only with the edge (x -> w), and so the other field names
74// fall back to lexicographical sorting.
75//
76// 5. Duplicates
77//
78// a: {z: _, y: _, z: int}
79//
80// b: c: _
81// b: d: _
82// b: c: int
83//
84// For a, we want to try to avoid adding an edge (y -> z), and for b
85// we want to try to avoid adding an edge (d -> c). So within a
86// regular struct, we do not add any additional edges when revisiting
87// a declaration previously visited within the same struct. Similarly,
88// for implicit unifications within the same file, we do not add any
89// additional edges when revisiting a declaration.
90//
91// In order to get as close as possible to the desired ordering, we
92// range over the Vertex's StructInfos, maintaining a list of Features
93// which must come before any new Features, i.e. a frontier. For this
94// to work, we need to sort the Vertex's StructInfos. Two approaches
95// are used:
96//
97// 1. A topological sorting of a Vertex's StructInfos. This is
98// effective for embeddings, and the relationship between embeddings
99// and regular fields. For example:
100//
101// a: {y: _, x: _}
102// b: {z: _, a}
103//
104// For b, a topological analysis will find that we can't enter the
105// StructInfo containing y and x, until after we've processed the
106// declaration of z.
107//
108// 2. However, even after a topological analysis, we'll often have
109// many root StructInfos. We order these by source position (not the
110// soure position of the StructInfo's StructLit itself, but of the
111// references (if any) that resolved to the StructInfo's StructLit),
112// then group them. If several StructInfos share the same position,
113// then they are batched together and considered to be explictly
114// unified. Then, consecutive batches of explicitly unified
115// StructInfos are grouped together.
116//
117// The result is that explicit unification is correctly
118// identified. E.g.:
119//
120// a: {x: _}
121// b: {z: int}
122// c: {y: >10}
123// o: a & b & c
124//
125// for o, the StructInfos corresponding to a, b and c will all be
126// grouped together in a single batch and considered to be explicitly
127// unified. Also, structInfos that correspond to the same position
128// (including no position) will be treated as explicity unified, and
129// so no weight will be given to their relative position within the
130// Vertex's slice of StructInfos.
131//
132// TODO: Switch if possible to finding if a struct has been unified
133// with a definition and as much as possible taking order from the
134// definition. In order words, if a cycle is only created by edges
135// that come from non-definitions, then we ignore those edges, and
136// thus don't end up dealing with a cycle.
137
138import (
139 "fmt"
140 "maps"
141 "slices"
142
143 "cuelang.org/go/cue/token"
144 "cuelang.org/go/internal/core/adt"
145)
146
147type structMeta struct {
148 structInfo adt.StructInfo
149 pos token.Pos
150
151 // Should this struct be considered to be part of an explicit
152 // unification (e.g. x & y)?
153 isExplicit bool
154}
155
156func (sMeta *structMeta) String() string {
157 sl := sMeta.structInfo.StructLit
158 return fmt.Sprintf("{%p sl:%p %v (explicit? %v)}",
159 sMeta, sl, sMeta.pos, sMeta.isExplicit)
160}
161
162func (sm *structMeta) hasDynamic(dynFieldsMap map[*adt.DynamicField][]adt.Feature) bool {
163 for _, decl := range sm.structInfo.Decls {
164 if dynField, ok := decl.(*adt.DynamicField); ok {
165 if _, found := dynFieldsMap[dynField]; found {
166 return true
167 }
168 }
169 }
170 return false
171}
172
173// We need to order a Vertex's StructInfos. To do that, we want a
174// filename+position for every StructInfo.
175//
176// We build a map from every StructInfo's StructLit and all its decls
177// to a *structMeta, using the structLit's position.
178//
179// The StructLit in a StructInfo may directly appear in the parent's
180// arc conjuncts. In this case, the StructLit's position is the
181// correct position to use. But the StructLit may have been reached
182// via a FieldReference, or SelectorExpr or something else. We want
183// the position of the reference, and not the StructLit itself. E.g.
184//
185// a: {x: 5}
186// b: {y: 7}
187// c: b
188// c: a
189//
190// If we're ordering the fields of c, we want the position of b and a
191// on lines 3 and 4, not the StructLits which declare a and b on lines
192// 1 and 2. To do this, we walk through the Vertex's Arc's
193// conjuncts. If a conjunct's Field has been reached via some
194// resolver, then the conjunct's Refs will record that, and will allow
195// us to update the Field's position (and hence the StructLit's
196// position) to that of the reference.
197//
198// Additionally, we need to discover whether each StructLit is
199// included as a result of explicit unification (c: a & b), implicit
200// unification:
201//
202// c: b
203// c: a
204//
205// or embedding:
206//
207// c: {
208// b
209// a
210// }
211//
212// Explicit unification needs treating specially so to avoid incorrect
213// edges between the fields of the lhs and rhs of the &. To do this,
214// we look at the vertex's conjuncts. If a conjunct is a binary
215// expression &, then we look up the structMeta for the arguments to
216// the binary expression, and mark them as explicit unification.
217func analyseStructs(v *adt.Vertex, builder *GraphBuilder) []structMeta {
218 structInfos := v.Structs
219 // Note that it's important that nodeToStructMetas avoids duplicate entries,
220 // which cause significant slowness for some large configs.
221 nodeToStructMetas := make(map[adt.Node]map[*structMeta]bool)
222 // structMetaMap is heplful as we can't insert into a map unless we make it.
223 structMetaMap := func(node adt.Node) map[*structMeta]bool {
224 if m := nodeToStructMetas[node]; m != nil {
225 return m
226 }
227 m := make(map[*structMeta]bool)
228 nodeToStructMetas[node] = m
229 return m
230 }
231
232 structMetas := make([]structMeta, len(structInfos))
233
234 // Create all the structMetas and map to them from a StructInfo's
235 // StructLit, and all its internal Decls. Initial attempt at
236 // recording a position, which will be correct only for direct use
237 // of literal structs in the calculation of vertex v.
238 metaIdx := 0
239 for _, s := range structInfos {
240 sl := s.StructLit
241 sMeta := &structMetas[metaIdx]
242 metaIdx++
243 sMeta.structInfo = s
244 sMeta.pos = adt.Pos(sl)
245
246 structMetaMap(sl)[sMeta] = true
247 for _, decl := range sl.Decls {
248 structMetaMap(decl)[sMeta] = true
249 }
250 }
251
252 // If an arc's conjunct's Field is a node we care about, and it has
253 // been reached via resolution, then unwind those resolutions to
254 // uncover the position of the earliest reference.
255 for _, arc := range v.Arcs {
256 builder.EnsureNode(arc.Label)
257 for c := range arc.LeafConjuncts() {
258 field := c.Field()
259 debug("self arc conjunct field %p :: %T, expr %p :: %T (%v)\n",
260 field, field, c.Expr(), c.Expr(), c.Expr().Source())
261 sMetas, found := nodeToStructMetas[field]
262 if !found {
263 continue
264 }
265 if src := field.Source(); src != nil {
266 pos := src.Pos()
267 for sMeta := range sMetas {
268 sMeta.pos = pos
269 }
270 }
271 refs := c.CloseInfo.CycleInfo.Refs
272 if refs == nil {
273 continue
274 }
275 debug(" ref %p :: %T (%v)\n",
276 refs.Ref, refs.Ref, refs.Ref.Source().Pos())
277 for refs.Next != nil {
278 refs = refs.Next
279 debug(" ref %p :: %T (%v)\n",
280 refs.Ref, refs.Ref, refs.Ref.Source().Pos())
281 }
282 maps.Insert(structMetaMap(refs.Ref), maps.All(sMetas))
283 if pos := refs.Ref.Source().Pos(); pos.IsValid() {
284 for sMeta := range nodeToStructMetas[refs.Ref] {
285 sMeta.pos = pos
286 }
287 }
288 }
289 }
290
291 // Explore our own conjuncts, and the decls from our StructList, to
292 // find explicit unifications, and mark structMetas accordingly.
293 var worklist []adt.Expr
294 for c := range v.LeafConjuncts() {
295 debug("self conjunct field %p :: %T, expr %p :: %T\n",
296 c.Field(), c.Field(), c.Expr(), c.Expr())
297 worklist = append(worklist, c.Expr())
298 }
299 for _, si := range structInfos {
300 for _, decl := range si.StructLit.Decls {
301 if expr, ok := decl.(adt.Expr); ok {
302 worklist = append(worklist, expr)
303 }
304 }
305 }
306
307 for len(worklist) != 0 {
308 expr := worklist[0]
309 worklist = worklist[1:]
310
311 binExpr, ok := expr.(*adt.BinaryExpr)
312 if !ok || binExpr.Op != adt.AndOp {
313 continue
314 }
315 for _, expr := range []adt.Expr{binExpr.X, binExpr.Y} {
316 for sMeta := range nodeToStructMetas[expr] {
317 sMeta.isExplicit = true
318 debug(" now explicit: %v\n", sMeta)
319 }
320 }
321 worklist = append(worklist, binExpr.X, binExpr.Y)
322 }
323
324 return structMetas
325}
326
327// Find all fields which have been created as a result of successful
328// evaluation of a dynamic field name.
329func dynamicFieldsFeatures(v *adt.Vertex) map[*adt.DynamicField][]adt.Feature {
330 var m map[*adt.DynamicField][]adt.Feature
331 for _, arc := range v.Arcs {
332 for c := range arc.LeafConjuncts() {
333 if dynField, ok := c.Field().(*adt.DynamicField); ok {
334 if m == nil {
335 m = make(map[*adt.DynamicField][]adt.Feature)
336 }
337 m[dynField] = append(m[dynField], arc.Label)
338 }
339 }
340 }
341 return m
342}
343
344type structMetaBatch []*structMeta
345
346func (batch structMetaBatch) isExplicit() bool {
347 return len(batch) > 1 || (len(batch) == 1 && batch[0].isExplicit)
348}
349
350type structMetaBatches []structMetaBatch
351
352func (batchesPtr *structMetaBatches) appendBatch(batch structMetaBatch) {
353 if len(batch) == 0 {
354 return
355 }
356 batches := *batchesPtr
357 if l := len(batches); l == 0 {
358 *batchesPtr = append(batches, batch)
359 } else if prevBatch := batches[l-1]; batch.isExplicit() &&
360 prevBatch.isExplicit() &&
361 batch[0].pos.Filename() == prevBatch[0].pos.Filename() {
362 batches[l-1] = append(batches[l-1], batch...)
363 } else {
364 *batchesPtr = append(batches, batch)
365 }
366}
367
368type vertexFeatures struct {
369 builder *GraphBuilder
370 dynFieldsMap map[*adt.DynamicField][]adt.Feature
371}
372
373func (vf *vertexFeatures) compareStructMeta(a, b structMeta) int {
374 if c := a.pos.Compare(b.pos); c != 0 {
375 return c
376 }
377 aHasDyn := a.hasDynamic(vf.dynFieldsMap)
378 bHasDyn := b.hasDynamic(vf.dynFieldsMap)
379 switch {
380 case aHasDyn == bHasDyn:
381 return 0
382 case aHasDyn:
383 return 1 // gather dynamic fields at the end
384 default:
385 return -1
386 }
387}
388
389func VertexFeatures(ctx *adt.OpContext, v *adt.Vertex) []adt.Feature {
390 debug("\n*** V (%s %v %p) ***\n", v.Label.SelectorString(ctx), v.Label, v)
391
392 builder := NewGraphBuilder(!ctx.Config.SortFields)
393 dynFieldsMap := dynamicFieldsFeatures(v)
394 roots := analyseStructs(v, builder)
395
396 vf := &vertexFeatures{
397 builder: builder,
398 dynFieldsMap: dynFieldsMap,
399 }
400
401 slices.SortFunc(roots, vf.compareStructMeta)
402 debug("roots: %v\n", roots)
403
404 var batches structMetaBatches
405 var batch structMetaBatch
406 for i := range roots {
407 root := &roots[i]
408 for range 1 + root.structInfo.Repeats {
409 if len(batch) == 0 ||
410 (batch[0].pos == root.pos && !root.hasDynamic(dynFieldsMap)) {
411 batch = append(batch, root)
412 } else {
413 batches.appendBatch(batch)
414 batch = structMetaBatch{root}
415 }
416 }
417 }
418 batches.appendBatch(batch)
419 debug("batches: %v\n", batches)
420
421 var previous, next []adt.Feature
422 var previousBatch structMetaBatch
423 for _, batch := range batches {
424 explicit := batch.isExplicit()
425 if len(previousBatch) != 0 &&
426 previousBatch[0].pos.Filename() != batch[0].pos.Filename() {
427 previous = nil
428 }
429 for _, root := range batch {
430 root.isExplicit = explicit
431 debug("starting root. Explicit unification? %v\n", explicit)
432 next = append(next, vf.addEdges(previous, root)...)
433 }
434 previous = next
435 next = nil
436 previousBatch = batch
437 }
438
439 debug("edges: %v\n", builder.edgesSet)
440 return builder.Build().Sort(ctx)
441}
442
443func (vf *vertexFeatures) addEdges(previous []adt.Feature, sMeta *structMeta) []adt.Feature {
444 debug("--- S %p (sl: %p) (explicit? %v) ---\n",
445 sMeta, sMeta.structInfo.StructLit, sMeta.isExplicit)
446 debug(" previous: %v\n", previous)
447 var next []adt.Feature
448
449 filename := sMeta.pos.Filename()
450 debug(" filename: %s (%v)\n", filename, sMeta.pos)
451
452 for i, decl := range sMeta.structInfo.Decls {
453 debug(" %p / %d: d (%p :: %T)\n", sMeta, i, decl, decl)
454 if bin, ok := decl.(*adt.BinaryExpr); ok {
455 debug(" binary expr: %p :: %T %v %p :: %T\n",
456 bin.X, bin.X, bin.Op, bin.Y, bin.Y)
457 }
458
459 currentLabel := adt.InvalidLabel
460 switch decl := decl.(type) {
461 case *adt.Field:
462 currentLabel = decl.Label
463 debug(" value %p :: %T (%v)\n", decl.Value, decl.Value, decl.Value)
464 if src := decl.Value.Source(); src != nil {
465 debug(" field value source: %v\n", src.Pos())
466 }
467 case *adt.DynamicField:
468 // This struct contains a dynamic field. If that dynamic
469 // field was successfully evaluated into a field, then insert
470 // that field into this chain.
471 if labels := vf.dynFieldsMap[decl]; len(labels) > 0 {
472 currentLabel = labels[0]
473 vf.dynFieldsMap[decl] = labels[1:]
474 }
475 }
476 if currentLabel != adt.InvalidLabel {
477 debug(" label %v\n", currentLabel)
478
479 node, exists := vf.builder.nodesByFeature[currentLabel]
480 if exists && node.structMeta == sMeta {
481 // same field within the same structLit
482 debug(" skipping 1\n")
483
484 } else if exists && !sMeta.isExplicit && sMeta.pos.IsValid() &&
485 node.structMeta != nil &&
486 node.structMeta.pos.Filename() == filename {
487 // same field within the same file during implicit unification
488 debug(" skipping 2\n")
489
490 } else {
491 debug(" %v %v\n", node, exists)
492 node = vf.builder.EnsureNode(currentLabel)
493 node.structMeta = sMeta
494 next = append(next, currentLabel)
495 for _, prevLabel := range previous {
496 vf.builder.AddEdge(prevLabel, currentLabel)
497 }
498 previous = next
499 next = nil
500 }
501 }
502 }
503
504 return previous
505}