Skip to content

Commit cc5aaa6

Browse files
committed
cue: changes ahead of unchecked unification
Introducing unchecked unification introduces some boilerplate-y changes. Changes this ahead of time to make the actual changes stand out. - Function to determine whether an operation is a unification op or not. - passing the type of unification to various functions where previously it was assumed to be opUnify. Issue #40 Change-Id: I1136a7341cf41b7a5ac47133996fd469d39a700f Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2869 Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent 3908dac commit cc5aaa6

File tree

6 files changed

+152
-123
lines changed

6 files changed

+152
-123
lines changed

cue/binop.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,16 @@ func binSrc(pos token.Pos, op op, a, b value) baseValue {
3434
}
3535

3636
func binOp(ctx *context, src source, op op, left, right evaluated) (result evaluated) {
37+
_, isUnify := op.unifyType()
3738
if b, ok := left.(*bottom); ok {
38-
if op == opUnify && b.exprDepth == 0 && cycleError(b) != nil {
39+
if isUnify && b.exprDepth == 0 && cycleError(b) != nil {
3940
ctx.cycleErr = true
4041
return right
4142
}
4243
return left
4344
}
4445
if b, ok := right.(*bottom); ok {
45-
if op == opUnify && b.exprDepth == 0 && cycleError(b) != nil {
46+
if isUnify && b.exprDepth == 0 && cycleError(b) != nil {
4647
ctx.cycleErr = true
4748
return left
4849
}
@@ -82,7 +83,7 @@ func binOp(ctx *context, src source, op op, left, right evaluated) (result evalu
8283
if invert {
8384
left, right = right, left
8485
}
85-
if op != opUnify {
86+
if !isUnify {
8687
// Any operation other than unification or disjunction must be on
8788
// concrete types. Disjunction is handled separately.
8889
if !leftKind.isGround() || !rightKind.isGround() {
@@ -94,7 +95,7 @@ func binOp(ctx *context, src source, op op, left, right evaluated) (result evalu
9495
return v
9596
}
9697

97-
// op == opUnify
98+
// isUnify
9899

99100
// TODO: unify type masks.
100101
if left == right {
@@ -108,13 +109,13 @@ func binOp(ctx *context, src source, op op, left, right evaluated) (result evalu
108109
}
109110

110111
if dl, ok := left.(*disjunction); ok {
111-
return distribute(ctx, src, dl, right)
112+
return distribute(ctx, src, op, dl, right)
112113
} else if dr, ok := right.(*disjunction); ok {
113-
return distribute(ctx, src, dr, left)
114+
return distribute(ctx, src, op, dr, left)
114115
}
115116

116117
if _, ok := right.(*unification); ok {
117-
return right.binOp(ctx, src, opUnify, left)
118+
return right.binOp(ctx, src, op, left)
118119
}
119120

120121
// TODO: value may be incomplete if there is a cycle. Instead of an error
@@ -141,21 +142,21 @@ type mVal struct {
141142
// TODO: this is an exponential algorithm. There is no reason to have to
142143
// resolve this early. Revise this to only do early pruning but not a full
143144
// evaluation.
144-
func distribute(ctx *context, src source, x, y evaluated) evaluated {
145+
func distribute(ctx *context, src source, op op, x, y evaluated) evaluated {
145146
dn := &disjunction{baseValue: src.base()}
146-
dist(ctx, dn, false, mVal{x, true}, mVal{y, true})
147+
dist(ctx, dn, false, op, mVal{x, true}, mVal{y, true})
147148
return dn.normalize(ctx, src).val
148149
}
149150

150-
func dist(ctx *context, d *disjunction, mark bool, x, y mVal) {
151+
func dist(ctx *context, d *disjunction, mark bool, op op, x, y mVal) {
151152
if dx, ok := x.val.(*disjunction); ok {
152153
if dx.hasDefaults {
153154
mark = true
154155
d.hasDefaults = true
155156
}
156157
for _, dxv := range dx.values {
157158
m := dxv.marked || !dx.hasDefaults
158-
dist(ctx, d, mark, mVal{dxv.val.evalPartial(ctx), m}, y)
159+
dist(ctx, d, mark, op, mVal{dxv.val.evalPartial(ctx), m}, y)
159160
}
160161
return
161162
}
@@ -166,12 +167,12 @@ func dist(ctx *context, d *disjunction, mark bool, x, y mVal) {
166167
}
167168
for _, dxy := range dy.values {
168169
m := dxy.marked || !dy.hasDefaults
169-
dist(ctx, d, mark, x, mVal{dxy.val.evalPartial(ctx), m})
170+
dist(ctx, d, mark, op, x, mVal{dxy.val.evalPartial(ctx), m})
170171
}
171172
return
172173
}
173-
src := binSrc(token.NoPos, opUnify, x.val, y.val)
174-
d.add(ctx, binOp(ctx, src, opUnify, x.val, y.val), mark && x.mark && y.mark)
174+
src := binSrc(token.NoPos, op, x.val, y.val)
175+
d.add(ctx, binOp(ctx, src, op, x.val, y.val), mark && x.mark && y.mark)
175176
}
176177

177178
func (x *disjunction) binOp(ctx *context, src source, op op, other evaluated) evaluated {
@@ -331,7 +332,6 @@ func (x *bound) binOp(ctx *context, src source, op op, other evaluated) evaluate
331332
k, _, msg := matchBinOpKind(opUnify, x.kind(), other.kind())
332333
if k == bottomKind {
333334
return ctx.mkErr(src, msg, opUnify, ctx.str(x), ctx.str(other), x.kind(), other.kind())
334-
break
335335
}
336336
switch y := other.(type) {
337337
case *basicType:
@@ -582,7 +582,8 @@ func evalLambda(ctx *context, a value) (l *lambdaExpr, err evaluated) {
582582

583583
func (x *structLit) binOp(ctx *context, src source, op op, other evaluated) evaluated {
584584
y, ok := other.(*structLit)
585-
if !ok || op != opUnify {
585+
_, isUnify := op.unifyType()
586+
if !ok || !isUnify {
586587
return ctx.mkIncompatible(src, op, x, other)
587588
}
588589

@@ -594,7 +595,14 @@ func (x *structLit) binOp(ctx *context, src source, op op, other evaluated) eval
594595
return x
595596
}
596597
arcs := make(arcs, 0, len(x.arcs)+len(y.arcs))
597-
obj := &structLit{binSrc(src.Pos(), op, x, other), x.emit, nil, nil, arcs, nil}
598+
obj := &structLit{
599+
binSrc(src.Pos(), op, x, other), // baseValue
600+
x.emit, // emit
601+
nil, // template
602+
nil, // comprehensions
603+
arcs, // arcs
604+
nil, // attributes
605+
}
598606
defer ctx.pushForwards(x, obj, y, obj).popForwards()
599607

600608
tx, ex := evalLambda(ctx, x.template)

cue/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func (x *bottom) Positions() []token.Pos {
147147
func appendPositions(pos []token.Pos, src source) []token.Pos {
148148
if src != nil {
149149
if b, ok := src.(*binaryExpr); ok {
150-
if b.op == opUnify {
150+
if _, isUnify := b.op.unifyType(); isUnify {
151151
pos = appendPositions(pos, b.left)
152152
pos = appendPositions(pos, b.right)
153153
}

cue/eval.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ func (x *binaryExpr) evalPartial(ctx *context) (result evaluated) {
365365
}
366366
var left, right evaluated
367367

368-
if x.op != opUnify {
368+
if _, isUnify := x.op.unifyType(); !isUnify {
369369
ctx.incEvalDepth()
370370
left = ctx.manifest(x.left)
371371
right = ctx.manifest(x.right)

cue/export.go

Lines changed: 102 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -352,104 +352,7 @@ func (p *exporter) expr(v value) ast.Expr {
352352
return bin
353353

354354
case *structLit:
355-
obj := &ast.StructLit{}
356-
if doEval(p.mode) {
357-
x = x.expandFields(p.ctx)
358-
}
359-
for _, a := range x.arcs {
360-
p.stack = append(p.stack, remap{
361-
key: x,
362-
from: a.feature,
363-
to: nil,
364-
syn: obj,
365-
})
366-
}
367-
if x.emit != nil {
368-
obj.Elts = append(obj.Elts, &ast.EmbedDecl{Expr: p.expr(x.emit)})
369-
}
370-
if !doEval(p.mode) && x.template != nil {
371-
l, ok := x.template.evalPartial(p.ctx).(*lambdaExpr)
372-
if ok {
373-
obj.Elts = append(obj.Elts, &ast.Field{
374-
Label: &ast.TemplateLabel{
375-
Ident: p.identifier(l.params.arcs[0].feature),
376-
},
377-
Value: p.expr(l.value),
378-
})
379-
} // TODO: else record error
380-
}
381-
for i, a := range x.arcs {
382-
f := &ast.Field{
383-
Label: p.label(a.feature),
384-
}
385-
// TODO: allow the removal of hidden fields. However, hidden fields
386-
// that still used in incomplete expressions should not be removed
387-
// (unless RequireConcrete is requested).
388-
if a.optional {
389-
// Optional fields are almost never concrete. We omit them in
390-
// concrete mode to allow the user to use the -a option in eval
391-
// without getting many errors.
392-
if p.mode.omitOptional || p.mode.concrete {
393-
continue
394-
}
395-
f.Optional = token.NoSpace.Pos()
396-
}
397-
if a.feature&hidden != 0 && p.mode.concrete && p.mode.omitHidden {
398-
continue
399-
}
400-
if !doEval(p.mode) {
401-
f.Value = p.expr(a.v)
402-
} else {
403-
e := x.at(p.ctx, i)
404-
if v := p.ctx.manifest(e); isIncomplete(v) && !p.mode.concrete && isBottom(e) {
405-
p := &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports}
406-
f.Value = p.expr(a.v)
407-
} else {
408-
f.Value = p.expr(e)
409-
}
410-
}
411-
if a.attrs != nil && !p.mode.omitAttrs {
412-
for _, at := range a.attrs.attr {
413-
f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
414-
}
415-
}
416-
obj.Elts = append(obj.Elts, f)
417-
}
418-
419-
for _, c := range x.comprehensions {
420-
var clauses []ast.Clause
421-
next := c.clauses
422-
for {
423-
if yield, ok := next.(*yield); ok {
424-
l := p.expr(yield.key)
425-
label, ok := l.(ast.Label)
426-
if !ok {
427-
// TODO: add an invalid field instead?
428-
continue
429-
}
430-
opt := token.NoPos
431-
if yield.opt {
432-
opt = token.NoSpace.Pos() // anything but token.NoPos
433-
}
434-
f := &ast.Field{
435-
Label: label,
436-
Optional: opt,
437-
Value: p.expr(yield.value),
438-
}
439-
var decl ast.Decl = f
440-
if len(clauses) > 0 {
441-
decl = &ast.ComprehensionDecl{Field: f, Clauses: clauses}
442-
}
443-
obj.Elts = append(obj.Elts, decl)
444-
break
445-
}
446-
447-
var y ast.Clause
448-
y, next = p.clause(next)
449-
clauses = append(clauses, y)
450-
}
451-
}
452-
return obj
355+
return p.structure(x)
453356

454357
case *fieldComprehension:
455358
panic("should be handled in structLit")
@@ -607,6 +510,107 @@ func (p *exporter) expr(v value) ast.Expr {
607510
}
608511
}
609512

513+
func (p *exporter) structure(x *structLit) *ast.StructLit {
514+
obj := &ast.StructLit{}
515+
if doEval(p.mode) {
516+
x = x.expandFields(p.ctx)
517+
}
518+
for _, a := range x.arcs {
519+
p.stack = append(p.stack, remap{
520+
key: x,
521+
from: a.feature,
522+
to: nil,
523+
syn: obj,
524+
})
525+
}
526+
if x.emit != nil {
527+
obj.Elts = append(obj.Elts, &ast.EmbedDecl{Expr: p.expr(x.emit)})
528+
}
529+
if !doEval(p.mode) && x.template != nil {
530+
l, ok := x.template.evalPartial(p.ctx).(*lambdaExpr)
531+
if ok {
532+
obj.Elts = append(obj.Elts, &ast.Field{
533+
Label: &ast.TemplateLabel{
534+
Ident: p.identifier(l.params.arcs[0].feature),
535+
},
536+
Value: p.expr(l.value),
537+
})
538+
} // TODO: else record error
539+
}
540+
for i, a := range x.arcs {
541+
f := &ast.Field{
542+
Label: p.label(a.feature),
543+
}
544+
// TODO: allow the removal of hidden fields. However, hidden fields
545+
// that still used in incomplete expressions should not be removed
546+
// (unless RequireConcrete is requested).
547+
if a.optional {
548+
// Optional fields are almost never concrete. We omit them in
549+
// concrete mode to allow the user to use the -a option in eval
550+
// without getting many errors.
551+
if p.mode.omitOptional || p.mode.concrete {
552+
continue
553+
}
554+
f.Optional = token.NoSpace.Pos()
555+
}
556+
if a.feature&hidden != 0 && p.mode.concrete && p.mode.omitHidden {
557+
continue
558+
}
559+
if !doEval(p.mode) {
560+
f.Value = p.expr(a.v)
561+
} else {
562+
e := x.at(p.ctx, i)
563+
if v := p.ctx.manifest(e); isIncomplete(v) && !p.mode.concrete && isBottom(e) {
564+
p := &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports}
565+
f.Value = p.expr(a.v)
566+
} else {
567+
f.Value = p.expr(e)
568+
}
569+
}
570+
if a.attrs != nil && !p.mode.omitAttrs {
571+
for _, at := range a.attrs.attr {
572+
f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
573+
}
574+
}
575+
obj.Elts = append(obj.Elts, f)
576+
}
577+
578+
for _, c := range x.comprehensions {
579+
var clauses []ast.Clause
580+
next := c.clauses
581+
for {
582+
if yield, ok := next.(*yield); ok {
583+
l := p.expr(yield.key)
584+
label, ok := l.(ast.Label)
585+
if !ok {
586+
// TODO: add an invalid field instead?
587+
continue
588+
}
589+
opt := token.NoPos
590+
if yield.opt {
591+
opt = token.NoSpace.Pos() // anything but token.NoPos
592+
}
593+
f := &ast.Field{
594+
Label: label,
595+
Optional: opt,
596+
Value: p.expr(yield.value),
597+
}
598+
var decl ast.Decl = f
599+
if len(clauses) > 0 {
600+
decl = &ast.ComprehensionDecl{Field: f, Clauses: clauses}
601+
}
602+
obj.Elts = append(obj.Elts, decl)
603+
break
604+
}
605+
606+
var y ast.Clause
607+
y, next = p.clause(next)
608+
clauses = append(clauses, y)
609+
}
610+
}
611+
return obj
612+
}
613+
610614
// quote quotes the given string.
611615
func quote(str string, quote byte) string {
612616
if strings.IndexByte(str, '\n') < 0 {

cue/op.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ func (op op) isCmp() bool {
133133
return opEql <= op && op <= opGeq
134134
}
135135

136+
func (op op) unifyType() (unchecked, ok bool) {
137+
return false, op == opUnify
138+
}
139+
136140
type op uint16
137141

138142
const (

0 commit comments

Comments
 (0)