Skip to content

Commit

Permalink
internal/core/export: implement self-contained algo for export
Browse files Browse the repository at this point in the history
The ensures  that all references pointing outside an exported
value (in Def) are either inlined or included as let, the
latter to avoid a possible exponential blowup.

This does not yet handle the case where a reference points to
an ancestor node of the exported value.

Issue #867

Signed-off-by: Marcel van Lohuizen <[email protected]>
Change-Id: Ie5a93949a68565ea60e954417bb2573fd36b0284
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/541464
Reviewed-by: Paul Jolly <[email protected]>
Unity-Result: CUEcueckoo <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
  • Loading branch information
mpvl committed Jul 29, 2022
1 parent bdaadc9 commit c71f674
Show file tree
Hide file tree
Showing 17 changed files with 1,194 additions and 6 deletions.
12 changes: 12 additions & 0 deletions internal/core/adt/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ func (c *OpContext) Logf(v *Vertex, format string, args ...interface{}) {
_ = log.Output(2, s)
}

// PathToString creates a pretty-printed path of the given list of features.
func (c *OpContext) PathToString(r Runtime, path []Feature) string {
var b strings.Builder
for i, f := range path {
if i > 0 {
b.WriteByte('.')
}
b.WriteString(f.SelectorString(c))
}
return b.String()
}

// Runtime defines an interface for low-level representation conversion and
// lookup.
type Runtime interface {
Expand Down
14 changes: 14 additions & 0 deletions internal/core/export/adt.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,12 @@ func (e *exporter) adt(env *adt.Environment, expr adt.Elem) ast.Expr {

case adt.Resolver:
return e.resolve(env, x)
}

e.inExpression++
defer func() { e.inExpression-- }()

switch x := expr.(type) {
case *adt.SliceExpr:
var lo, hi ast.Expr
if x.Lo != nil {
Expand Down Expand Up @@ -275,6 +280,15 @@ func (e *exporter) adt(env *adt.Environment, expr adt.Elem) ast.Expr {
var dummyTop = &ast.Ident{Name: "_"}

func (e *exporter) resolve(env *adt.Environment, r adt.Resolver) ast.Expr {
if c := e.pivotter; c != nil {
if alt := c.refExpr(r); alt != nil {
return alt
}
}

e.inExpression++
defer func() { e.inExpression-- }()

switch x := r.(type) {
case *adt.FieldReference:
ident, _ := e.newIdentForField(x.Src, x.Label, x.UpCount)
Expand Down
51 changes: 45 additions & 6 deletions internal/core/export/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ type Profile struct {
// Use unevaluated conjuncts for these error types
// IgnoreRecursive

// TODO: recurse over entire tree to determine transitive closure
// of what needs to be printed.
// IncludeDependencies bool
// SelfContained exports a schema such that it does not rely on any imports.
SelfContained bool

// InlineImports expands references to non-builtin packages.
InlineImports bool
}

var Simplified = &Profile{
Expand Down Expand Up @@ -92,13 +94,16 @@ var All = &Profile{
// Concrete

// Def exports v as a definition.
// It resolves references that point outside any of the vertices in v.
func Def(r adt.Runtime, pkgID string, v *adt.Vertex) (*ast.File, errors.Error) {
return All.Def(r, pkgID, v)
}

// Def exports v as a definition.
func (p *Profile) Def(r adt.Runtime, pkgID string, v *adt.Vertex) (*ast.File, errors.Error) {
// It resolves references that point outside any of the vertices in v.
func (p *Profile) Def(r adt.Runtime, pkgID string, v *adt.Vertex) (f *ast.File, err errors.Error) {
e := newExporter(p, r, pkgID, v)
e.initPivot(v)

isDef := v.IsRecursivelyClosed()
if isDef {
Expand All @@ -116,13 +121,18 @@ func (p *Profile) Def(r adt.Runtime, pkgID string, v *adt.Vertex) (*ast.File, er
)
}
}

return e.finalize(v, expr)
}

// Expr exports the given unevaluated expression (schema mode).
// It does not resolve references that point outside the given expession.
func Expr(r adt.Runtime, pkgID string, n adt.Expr) (ast.Expr, errors.Error) {
return Simplified.Expr(r, pkgID, n)
}

// Expr exports the given unevaluated expression (schema mode).
// It does not resolve references that point outside the given expression.
func (p *Profile) Expr(r adt.Runtime, pkgID string, n adt.Expr) (ast.Expr, errors.Error) {
e := newExporter(p, r, pkgID, nil)

Expand Down Expand Up @@ -170,21 +180,33 @@ func (e *exporter) toFile(v *adt.Vertex, x ast.Expr) *ast.File {
return f
}

// Vertex exports evaluated values (data mode).
// It resolves incomplete references that point outside the current context.
func Vertex(r adt.Runtime, pkgID string, n *adt.Vertex) (*ast.File, errors.Error) {
return Simplified.Vertex(r, pkgID, n)
}

func (p *Profile) Vertex(r adt.Runtime, pkgID string, n *adt.Vertex) (*ast.File, errors.Error) {
// Vertex exports evaluated values (data mode).
// It resolves incomplete references that point outside the current context.
func (p *Profile) Vertex(r adt.Runtime, pkgID string, n *adt.Vertex) (f *ast.File, err errors.Error) {
e := newExporter(p, r, pkgID, n)
e.initPivot(n)

v := e.value(n, n.Conjuncts...)
return e.finalize(n, v)
}

// Value exports evaluated values (data mode).
// It does not resolve references that point outside the given Value.
func Value(r adt.Runtime, pkgID string, n adt.Value) (ast.Expr, errors.Error) {
return Simplified.Value(r, pkgID, n)
}

// Should take context.
// Value exports evaluated values (data mode).
//
// It does not resolve references that point outside the given Value.
//
// TODO: Should take context.
func (p *Profile) Value(r adt.Runtime, pkgID string, n adt.Value) (ast.Expr, errors.Error) {
e := newExporter(p, r, pkgID, n)
v := e.value(n)
Expand All @@ -204,6 +226,7 @@ type exporter struct {
stack []frame

inDefinition int // for close() wrapping.
inExpression int // for inlining decisions.

// hidden label handling
pkgID string
Expand All @@ -217,6 +240,8 @@ type exporter struct {
letAlias map[*ast.LetClause]*ast.LetClause

usedHidden map[string]bool

pivotter *pivotter
}

// newExporter creates and initializes an exporter.
Expand All @@ -234,11 +259,25 @@ func newExporter(p *Profile, r adt.Runtime, pkgID string, v adt.Value) *exporter
return e
}

// initPivot initializes the pivotter to allow aligning a configuration around
// a new root, if needed.
func (e *exporter) initPivot(n *adt.Vertex) {
if !e.cfg.InlineImports &&
!e.cfg.SelfContained &&
n.Parent == nil {
return
}

e.initPivotter(n)
}

// finalize finalizes the result of an export. It is only needed for use cases
// that require conversion to a File, Sanitization, and self containment.
func (e *exporter) finalize(n *adt.Vertex, v ast.Expr) (f *ast.File, err errors.Error) {
f = e.toFile(n, v)

e.completePivot(f)

if err := astutil.Sanitize(f); err != nil {
err := errors.Promote(err, "export")
return f, errors.Append(e.errs, err)
Expand Down
Loading

0 comments on commit c71f674

Please sign in to comment.