this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/core/layer: rudimentary implementation of layers

This is not a language-level feature, but rather
something that can be controlled at the compilation
layer.

This supports:
- favoring one default over another
- treating data as defaults

It does not support:
- treating lists as scalars (it should)
- intuitive resolution of disjunctions

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I1859fc9e253235b8f35645d1a834afd813cc5fba
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1224786
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>

+402 -15
+23
cue/token/position.go
··· 20 20 "sort" 21 21 "sync" 22 22 23 + "cuelang.org/go/internal/core/layer" 23 24 "cuelang.org/go/internal/cueexperiment" 24 25 ) 25 26 ··· 92 93 93 94 x = *p.file.experiments 94 95 return x 96 + } 97 + 98 + // NOTE: this is an internal API and may change at any time without notice. 99 + func (p hiddenPos) Priority() (pr layer.Priority, ok bool) { 100 + if f := p.file; f != nil { 101 + return f.p, f.isData 102 + } 103 + return 0, false 95 104 } 96 105 97 106 // TODO(mvdan): The methods below don't need to build an entire Position ··· 263 272 content []byte 264 273 265 274 experiments *cueexperiment.File 275 + p layer.Priority 276 + isData bool 266 277 } 267 278 268 279 // NewFile returns a new file with the given OS file name. The size provides the ··· 300 311 301 312 func (f *hiddenFile) SetExperiments(experiments *cueexperiment.File) { 302 313 f.experiments = experiments 314 + } 315 + 316 + // NOTE: this is an internal API and may change at any time without notice. 317 + // 318 + // SetLayer sets the layer priority for this file. The priority parameter 319 + // determines the precedence of defaults defined in this file, with higher 320 + // values taking precedence over lower values. The isData parameter indicates 321 + // whether this file should be treated as containing data defaults, which 322 + // have different merging semantics from regular defaults. 323 + func (f *hiddenFile) SetLayer(priority int8, isData bool) { 324 + f.p = layer.Priority(priority) 325 + f.isData = isData 303 326 } 304 327 305 328 // Name returns the file name of file f as registered with AddFile.
+7
internal/core/adt/closed.go
··· 14 14 15 15 package adt 16 16 17 + import "cuelang.org/go/internal/core/layer" 18 + 17 19 // This file implements the closedness algorithm. 18 20 19 21 // Outline of algorithm ··· 101 103 102 104 // This conjunct was opened by the ... postfix operator. 103 105 Opened bool 106 + 107 + // Priority is used for default resolution. Higher values win. 0 means no 108 + // priority is assigned. Default handling may be more restrictive than 109 + // specified in the spec when a priority is assigned. 110 + Priority layer.Priority 104 111 105 112 CycleInfo 106 113 }
+18
internal/core/adt/conjunct.go
··· 670 670 671 671 case Value: // *NullLit, *BoolLit, *NumLit, *StringLit, *BytesLit, *Builtin 672 672 n.unshare() 673 + if p, isData := pos(v).Priority(); isData { 674 + id.Priority = p 675 + } 676 + 673 677 n.updateCyclicStatusV3(id) 674 678 675 679 if y := n.scalar; y != nil { 680 + p1 := n.scalarID.Priority 681 + p2 := id.Priority 682 + if p1 != 0 && p2 != 0 { 683 + if p1 > p2 { 684 + // all good 685 + break 686 + } else if p1 < p2 { 687 + goto patchConjunct 688 + } 689 + } 676 690 if b, ok := BinOp(ctx, errOnDiffType, EqualOp, x, y).(*Bool); !ok || !b.B { 677 691 n.reportConflict(x, y, x.Kind(), y.Kind(), n.scalarID, id) 678 692 } 679 693 break 680 694 } 695 + patchConjunct: 681 696 n.scalar = x 682 697 n.scalarID = id 698 + // TODO: only set "scalarKnown" if there are no other high priority 699 + // conjuncts. Alternatively, we should process high priority conjuncts 700 + // in the scheduler first. 683 701 n.signal(scalarKnown) 684 702 685 703 default:
+49 -7
internal/core/adt/disjunct2.go
··· 14 14 15 15 package adt 16 16 17 - import "slices" 17 + import ( 18 + "math" 19 + "slices" 20 + 21 + "cuelang.org/go/internal/core/layer" 22 + ) 18 23 19 24 // # Overview 20 25 // ··· 392 397 393 398 leftDropsDefault := true 394 399 rightDropsDefault := true 400 + priority, _ := pos(dn.src).Priority() 395 401 396 402 for i, p := range cross { 397 403 ID := n.nextCrossProduct(i, len(cross), p) ··· 415 421 continue 416 422 } 417 423 424 + // Promote the priority of defaults. 425 + r.origPriority = priority 426 + r.priority = p.origPriority 427 + if p.defaultMode == isDefault && p.origPriority > priority { 428 + r.origPriority = priority 429 + } 430 + 418 431 tmp = append(tmp, r) 419 432 if p.defaultMode == isDefault || p.origDefaultMode == isDefault { 420 433 leftDropsDefault = false ··· 430 443 // Unroll nested disjunctions. 431 444 switch len(r.disjuncts) { 432 445 case 0: 446 + // If a default is not dropped in a higher priority, allow still to 447 + // become a default, even if other other side has dropped defaults. 448 + if !rightDropsDefault && r.origDefaultMode == notDefault && 449 + priority < r.priority { 450 + r.origDefaultMode = maybeDefault 451 + } 452 + if !leftDropsDefault && r.defaultMode == notDefault && 453 + priority > r.priority { 454 + r.defaultMode = maybeDefault 455 + } 456 + 433 457 r.defaultMode = combineDefault2(r.defaultMode, r.origDefaultMode, leftDropsDefault, rightDropsDefault) 434 458 // r did not have a nested disjunction. 435 459 dst = appendDisjunct(n.ctx, dst, r) ··· 556 580 // a special mode, or evaluating more aggressively if finalize is not given. 557 581 v.status = unprocessed 558 582 583 + if m == isDefault { 584 + c.CloseInfo.Priority, _ = pos(c.x).Priority() 585 + } 559 586 d.scheduleConjunct(c, c.CloseInfo) 560 587 561 588 oc.unlinkOverlay() ··· 605 632 return 606 633 } 607 634 635 + // Determine highest priority and if the default exists. 636 + hasDefaults := false 637 + defaultPriority := layer.Priority(math.MinInt8) 638 + for _, x := range n.disjuncts { 639 + if x.defaultMode == isDefault { 640 + hasDefaults = true 641 + if x.origPriority > defaultPriority { 642 + defaultPriority = x.origPriority 643 + } 644 + } 645 + } 646 + 608 647 a := make([]Value, len(n.disjuncts)) 609 648 p := 0 610 - hasDefaults := false 611 649 for i, x := range n.disjuncts { 650 + if x.origPriority < defaultPriority && x.defaultMode != isDefault { 651 + x.defaultMode = notDefault 652 + } 653 + 612 654 switch x.defaultMode { 613 655 case isDefault: 614 - a[i] = a[p] 615 - a[p] = x.node 616 - p++ 617 - hasDefaults = true 618 - 656 + if x.origPriority == defaultPriority { 657 + a[i] = a[p] 658 + a[p] = x.node 659 + p++ 660 + } 619 661 case notDefault: 620 662 hasDefaults = true 621 663 fallthrough
+18 -7
internal/core/adt/eval.go
··· 23 23 "cuelang.org/go/cue/errors" 24 24 "cuelang.org/go/cue/stats" 25 25 "cuelang.org/go/cue/token" 26 + "cuelang.org/go/internal/core/layer" 26 27 ) 27 28 28 29 // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO ··· 386 387 shareDecremented bool // counters of sharedIDs have been decremented 387 388 388 389 depth int32 389 - defaultMode defaultMode // cumulative default mode 390 - origDefaultMode defaultMode // default mode of the original disjunct 390 + defaultMode defaultMode // cumulative default mode 391 + origDefaultMode defaultMode // default mode of the original disjunct 392 + priority layer.Priority // Priority corresponding to defaultMode 393 + origPriority layer.Priority // Priority of the original disjunct 391 394 392 395 // has a value filled out before the node splits into a disjunction. Aside 393 396 // from detecting a self-reference cycle when there is otherwise just an ··· 442 445 443 446 // Value info 444 447 445 - kind Kind 446 - kindExpr Expr // expr that adjust last value (for error reporting) 447 - kindID CloseInfo // for error tracing 448 + kind Kind 449 + constraintKind Kind 450 + defaultKind Kind 451 + kindExpr Expr // expr that adjust last value (for error reporting) 452 + kindID CloseInfo // for error tracing 448 453 449 454 // Current value (may be under construction) 450 455 scalar Value // TODO: use Value in node. ··· 491 496 scheduler: n.scheduler, 492 497 node: node, 493 498 nodeContextState: nodeContextState{ 494 - kind: TopKind, 499 + kind: TopKind, 500 + constraintKind: TopKind, 501 + defaultKind: TopKind, 495 502 }, 496 503 toFree: n.toFree[:0], 497 504 arcMap: n.arcMap[:0], ··· 521 528 }, 522 529 node: node, 523 530 524 - nodeContextState: nodeContextState{kind: TopKind}, 531 + nodeContextState: nodeContextState{ 532 + kind: TopKind, 533 + constraintKind: TopKind, 534 + defaultKind: TopKind, 535 + }, 525 536 } 526 537 } 527 538
+1 -1
internal/core/adt/tasks.go
··· 258 258 259 259 var ellipsis Node 260 260 261 - id := t.id 261 + id := c.subField(t.id) 262 262 263 263 index := int64(0) 264 264 hasComprehension := false
+4
internal/core/adt/typocheck.go
··· 416 416 // as, at this point, it seems to be only used for debugging. We may 417 417 // want to consider having a separate field for this, though. 418 418 ci.FromEmbed = false 419 + 420 + // The priority should be cleared for sub fields: we take default struct 421 + // in its entirety, but not piecemeal. 422 + ci.Priority = 0 419 423 return ci 420 424 } 421 425
+30
internal/core/layer/layer.go
··· 1 + // Copyright 2025 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 + // https://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 layer 16 + 17 + type Priority int8 18 + 19 + // TODO: algorithm for handling disjunctions more intuitively with layering. 20 + // For instance, how do we handle this case?: 21 + // 22 + // x: {} | *{ 23 + // b: x: 1 24 + // c: x: 2 25 + // } 26 + // // If y > x, does the 2 of b force the default of x to fail? Could be an option. 27 + // y: { 28 + // b: *{x: 2} | {} 29 + // c: *{x: 3} | {} 30 + // }
+86
internal/core/layer/layer_test.go
··· 1 + // Copyright 2025 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 + // https://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 layer_test 16 + 17 + import ( 18 + "fmt" 19 + "slices" 20 + "strconv" 21 + "strings" 22 + "testing" 23 + 24 + "cuelang.org/go/cue/ast" 25 + "cuelang.org/go/cue/cuecontext" 26 + "cuelang.org/go/cue/errors" 27 + "cuelang.org/go/internal/core/adt" 28 + "cuelang.org/go/internal/cuedebug" 29 + "cuelang.org/go/internal/cuetxtar" 30 + ) 31 + 32 + func TestLayers(t *testing.T) { 33 + adt.DebugDeps = true // check unmatched dependencies. 34 + 35 + test := cuetxtar.TxTarTest{ 36 + Root: "./testdata", 37 + Name: "layer", 38 + } 39 + 40 + cuedebug.Init() 41 + 42 + test.Run(t, func(t *cuetxtar.Test) { 43 + a := t.Instance() 44 + ctx := cuecontext.New() 45 + 46 + // Inject layering based on @layer attribute. 47 + for _, f := range a.Files { 48 + for i, d := range f.Decls { 49 + _ = i 50 + attr, ok := d.(*ast.Attribute) 51 + if !ok { 52 + // no preamble 53 + break 54 + } 55 + key, body := attr.Split() 56 + if key != "layer" { 57 + continue 58 + } 59 + 60 + args := strings.Split(body, ",") 61 + 62 + priority, err := strconv.ParseInt(args[0], 10, 8) 63 + if err != nil { 64 + t.Fatalf("invalid priority %q: %v", args[0], err) 65 + } 66 + 67 + f.Pos().File().SetLayer( 68 + int8(priority), 69 + slices.Contains(args, "defaultData"), 70 + ) 71 + 72 + f.Decls = slices.Delete(f.Decls, i, i+1) 73 + break 74 + } 75 + } 76 + 77 + v := ctx.BuildInstance(a) 78 + if err := v.Err(); err != nil { 79 + t.WriteErrors(errors.Promote(err, "layers")) 80 + return 81 + } 82 + 83 + s := fmt.Sprint(v) 84 + t.Write([]byte(s)) 85 + }) 86 + }
+21
internal/core/layer/testdata/data.txtar
··· 1 + -- a.cue -- 2 + @layer(2,defaultData) 3 + x: { 4 + b: { x: 1 } 5 + c: { x: 2 } 6 + } 7 + -- b.cue -- 8 + @layer(3,defaultData) 9 + x: c: { x: 3 } 10 + 11 + -- out/layer -- 12 + { 13 + x: { 14 + b: { 15 + x: 1 16 + } 17 + c: { 18 + x: 3 19 + } 20 + } 21 + }
+10
internal/core/layer/testdata/data_list.txtar
··· 1 + -- a.cue -- 2 + @layer(2,defaultData) 3 + list: [2] 4 + -- b.cue -- 5 + @layer(3,defaultData) 6 + list: [3] 7 + -- out/layer -- 8 + { 9 + list: [2] 10 + }
+16
internal/core/layer/testdata/data_mixed.txtar
··· 1 + -- a.cue -- 2 + @layer(2,defaultData) 3 + mixed: { 4 + fromData: 1 5 + fromBoth: 2 6 + } 7 + -- b.cue -- 8 + @layer(3) 9 + mixed: { 10 + fromNormal: 10 11 + fromBoth: 20 12 + } 13 + -- out/layer -- 14 + mixed.fromBoth: conflicting values 20 and 2: 15 + ./a.cue:4:12 16 + ./b.cue:4:12
+20
internal/core/layer/testdata/data_nested_lists.txtar
··· 1 + # TODO: treat lists as "scalars" in defaultsData mode 2 + -- a.cue -- 3 + @layer(2,defaultData) 4 + nested: { 5 + items: [1, 2] 6 + config: { 7 + values: ["a"] 8 + } 9 + } 10 + -- b.cue -- 11 + @layer(3,defaultData) 12 + nested: { 13 + items: [3, 4, 5] 14 + config: { 15 + values: ["b", "c"] 16 + } 17 + } 18 + -- out/layer -- 19 + nested.items: incompatible list lengths (2 and 3): 20 + ./b.cue:3:9
+16
internal/core/layer/testdata/observe_costraints.txtar
··· 1 + -- low.cue -- 2 + @layer(3) 3 + 4 + a: int | *1 5 + b: _ 6 + c: a & b 7 + -- high.cue -- 8 + @layer(5) 9 + 10 + b: int | *"foo" // foo does not unify with int, so is eliminated 11 + -- out/layer -- 12 + { 13 + a: *1 | int 14 + b: *"foo" | int 15 + c: *1 | int 16 + }
+13
internal/core/layer/testdata/scalar.txtar
··· 1 + -- low.cue -- 2 + @layer(3) 3 + 4 + a: int | *1 5 + 6 + -- high.cue -- 7 + @layer(5) 8 + 9 + a: int | *2 10 + -- out/layer -- 11 + { 12 + a: *2 | 1 | int 13 + }
+26
internal/core/layer/testdata/scalar_cross_layer.txtar
··· 1 + -- low.cue -- 2 + @layer(3) 3 + 4 + a1: int | *1 5 + b1: _ 6 + c1: a1 & b1 7 + 8 + a2: _ | *1 9 + b2: _ 10 + c2: a2 & b2 11 + 12 + -- high.cue -- 13 + @layer(5) 14 + 15 + b1: int | *2 16 + 17 + b2: _ | *"foo" 18 + -- out/layer -- 19 + { 20 + a1: *1 | int 21 + b1: *2 | int 22 + c1: *2 | int | 1 23 + a2: *1 | _ 24 + b2: *"foo" | _ 25 + c2: *"foo" | _ | 1 26 + }
+22
internal/core/layer/testdata/scalar_multiple_defaults.txtar
··· 1 + -- a.cue -- 2 + @layer(2) 3 + 4 + x: string | *"low" 5 + y: int | *10 6 + -- b.cue -- 7 + @layer(4) 8 + 9 + x: string | *"medium" 10 + z: bool | *true 11 + -- c.cue -- 12 + @layer(6) 13 + 14 + x: string | *"high" 15 + y: int | *20 16 + z: bool 17 + -- out/layer -- 18 + { 19 + x: *"high" | string | "medium" | "low" 20 + y: *20 | int | 10 21 + z: *true | bool 22 + }
+22
internal/core/layer/testdata/scalar_nested_defaults.txtar
··· 1 + -- a.cue -- 2 + @layer(3) 3 + 4 + config: { 5 + port: int | *8080 6 + host: string | *"localhost" 7 + } 8 + -- b.cue -- 9 + @layer(5) 10 + 11 + config: { 12 + port: int | *9000 13 + timeout: int | *30 14 + } 15 + -- out/layer -- 16 + { 17 + config: { 18 + port: *9000 | int | 8080 19 + host: *"localhost" | string 20 + timeout: *30 | int 21 + } 22 + }