cuelang.org/go@v0.13.0/tools/trim/trimv2.go (about)

     1  // Copyright 2018 The 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 trim
    16  
    17  import (
    18  	"io"
    19  	"os"
    20  
    21  	"cuelang.org/go/cue"
    22  	"cuelang.org/go/cue/ast"
    23  	"cuelang.org/go/cue/ast/astutil"
    24  	"cuelang.org/go/internal/core/adt"
    25  	"cuelang.org/go/internal/core/debug"
    26  	"cuelang.org/go/internal/core/eval"
    27  	"cuelang.org/go/internal/core/subsume"
    28  	"cuelang.org/go/internal/core/walk"
    29  	"cuelang.org/go/internal/value"
    30  )
    31  
    32  // Files trims fields in the given files that can be implied from other fields,
    33  // as can be derived from the evaluated values in inst.
    34  // Trimming is done on a best-effort basis and only when the removed field
    35  // is clearly implied by another field, rather than equal sibling fields.
    36  func filesV2(files []*ast.File, val cue.Value, cfg *Config) error {
    37  	r, v := value.ToInternal(val)
    38  
    39  	t := &trimmerV2{
    40  		Config:  *cfg,
    41  		ctx:     eval.NewContext(r, v),
    42  		remove:  map[ast.Node]bool{},
    43  		exclude: map[ast.Node]bool{},
    44  		debug:   Debug,
    45  		w:       os.Stderr,
    46  	}
    47  
    48  	// Mark certain expressions as off limits.
    49  	// TODO: We could alternatively ensure that comprehensions unconditionally
    50  	// resolve.
    51  	visitor := &walk.Visitor{
    52  		Before: func(n adt.Node) bool {
    53  			switch x := n.(type) {
    54  			case *adt.StructLit:
    55  				// Structs with comprehensions may never be removed.
    56  				for _, d := range x.Decls {
    57  					switch d.(type) {
    58  					case *adt.Comprehension:
    59  						t.markKeep(x)
    60  					}
    61  				}
    62  			}
    63  			return true
    64  		},
    65  	}
    66  	v.VisitLeafConjuncts(func(c adt.Conjunct) bool {
    67  		visitor.Elem(c.Elem())
    68  		return true
    69  	})
    70  
    71  	d, _, _, pickedDefault := t.addDominators(nil, v, false)
    72  	t.findSubordinates(d, v, pickedDefault)
    73  
    74  	// Remove subordinate values from files.
    75  	for _, f := range files {
    76  		astutil.Apply(f, func(c astutil.Cursor) bool {
    77  			if f, ok := c.Node().(*ast.Field); ok && t.remove[f.Value] && !t.exclude[f.Value] {
    78  				c.Delete()
    79  			}
    80  			return true
    81  		}, nil)
    82  		if err := astutil.Sanitize(f); err != nil {
    83  			return err
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  type trimmerV2 struct {
    91  	Config
    92  
    93  	ctx     *adt.OpContext
    94  	remove  map[ast.Node]bool
    95  	exclude map[ast.Node]bool
    96  
    97  	debug  bool
    98  	indent int
    99  	w      io.Writer
   100  }
   101  
   102  var Debug bool = false
   103  
   104  func (t *trimmerV2) markRemove(c adt.Conjunct) {
   105  	if src := c.Elem().Source(); src != nil {
   106  		t.remove[src] = true
   107  		if t.debug {
   108  			t.logf("removing %s", debug.NodeString(t.ctx, c.Elem(), nil))
   109  		}
   110  	}
   111  }
   112  
   113  func (t *trimmerV2) markKeep(x adt.Expr) {
   114  	if src := x.Source(); src != nil {
   115  		t.exclude[src] = true
   116  		if t.debug {
   117  			t.logf("keeping")
   118  		}
   119  	}
   120  }
   121  
   122  const dominatorNode = adt.ComprehensionSpan | adt.DefinitionSpan | adt.ConstraintSpan
   123  
   124  // isDominator reports whether a node can remove other nodes.
   125  func isDominator(c adt.Conjunct) (ok, mayRemove bool) {
   126  	if !c.CloseInfo.IsInOneOf(dominatorNode) {
   127  		return false, false
   128  	}
   129  	switch f := c.Field().(type) {
   130  	case *adt.Field: // bulk constraints handled elsewhere.
   131  		return true, f.ArcType == adt.ArcMember
   132  	}
   133  	return true, true
   134  }
   135  
   136  // Removable reports whether a non-dominator conjunct can be removed. This is
   137  // not the case if it has pattern constraints that could turn into dominator
   138  // nodes.
   139  func removable(c adt.Conjunct, v *adt.Vertex) bool {
   140  	return c.CloseInfo.FieldTypes&(adt.HasAdditional|adt.HasPattern) == 0
   141  }
   142  
   143  // Roots of constraints are not allowed to strip conjuncts by
   144  // themselves as it will eliminate the reason for the trigger.
   145  func (t *trimmerV2) allowRemove(v *adt.Vertex) (allow bool) {
   146  	v.VisitLeafConjuncts(func(c adt.Conjunct) bool {
   147  		_, allowRemove := isDominator(c)
   148  		loc := c.CloseInfo.Location() != c.Elem()
   149  		isSpan := c.CloseInfo.RootSpanType() != adt.ConstraintSpan
   150  		if allowRemove && (loc || isSpan) {
   151  			allow = true
   152  			return false
   153  		}
   154  		return true
   155  	})
   156  	return allow
   157  }
   158  
   159  // A parent may be removed if there is not a `no` and there is at least one
   160  // `yes`. A `yes` is proves that there is at least one node that is not a
   161  // dominator node and that we are not removing nodes from a declaration of a
   162  // dominator itself.
   163  const (
   164  	no = 1 << iota
   165  	maybe
   166  	yes
   167  )
   168  
   169  // addDominators injects dominator values from v into d. If no default has
   170  // been selected from dominators so far, the values are erased. Otherwise,
   171  // both default and new values are merged.
   172  //
   173  // Erasing the previous values when there has been no default so far allows
   174  // interpolations, for instance, to be evaluated in the new context and
   175  // eliminated.
   176  //
   177  // Values are kept when there has been a default (current or ancestor) because
   178  // the current value may contain information that caused that default to be
   179  // selected and thus erasing it would cause that information to be lost.
   180  //
   181  // TODO:
   182  // In principle, information only needs to be kept for discriminator values, or
   183  // any value that was instrumental in selecting the default. This is currently
   184  // hard to do, however, so we just fall back to a stricter mode in the presence
   185  // of defaults.
   186  func (t *trimmerV2) addDominators(d, v *adt.Vertex, hasDisjunction bool) (doms *adt.Vertex, ambiguous, hasSubs, strict bool) {
   187  	strict = hasDisjunction
   188  	doms = &adt.Vertex{
   189  		Parent: v.Parent,
   190  		Label:  v.Label,
   191  	}
   192  	if d != nil && hasDisjunction {
   193  		doms.InsertConjunctsFrom(d)
   194  	}
   195  
   196  	hasDoms := false
   197  	v.VisitLeafConjuncts(func(c adt.Conjunct) bool {
   198  		isDom, _ := isDominator(c)
   199  		switch {
   200  		case isDom:
   201  			doms.AddConjunct(c)
   202  		default:
   203  			if r, ok := c.Elem().(adt.Resolver); ok {
   204  				x, _ := t.ctx.Resolve(c, r)
   205  				// Even if this is not a dominator now, descendants will be.
   206  				if x != nil && x.Label.IsDef() {
   207  					x.VisitLeafConjuncts(func(c adt.Conjunct) bool {
   208  						doms.AddConjunct(c)
   209  						return true
   210  					})
   211  					return false
   212  				}
   213  			}
   214  			hasSubs = true
   215  		}
   216  		return true
   217  	})
   218  	doms.Finalize(t.ctx)
   219  
   220  	switch x := doms.Value().(type) {
   221  	case *adt.Disjunction:
   222  		switch x.NumDefaults {
   223  		case 1:
   224  			strict = true
   225  		default:
   226  			ambiguous = true
   227  		}
   228  	}
   229  
   230  	if doms = doms.Default(); doms.IsErr() {
   231  		ambiguous = true
   232  	}
   233  
   234  	_ = hasDoms
   235  	return doms, hasSubs, ambiguous, strict || ambiguous
   236  }
   237  
   238  func (t *trimmerV2) findSubordinates(doms, v *adt.Vertex, hasDisjunction bool) (result int) {
   239  	defer un(t.trace(v))
   240  	defer func() {
   241  		if result == no {
   242  			v.VisitLeafConjuncts(func(c adt.Conjunct) bool {
   243  				t.markKeep(c.Expr())
   244  				return true
   245  			})
   246  		}
   247  	}()
   248  
   249  	doms, hasSubs, ambiguous, pickedDefault := t.addDominators(doms, v, hasDisjunction)
   250  
   251  	if ambiguous {
   252  		return no
   253  	}
   254  
   255  	// TODO(structure sharing): do not descend into vertices whose parent is not
   256  	// equal to the parent. This is not relevant at this time, but may be so in
   257  	// the future.
   258  
   259  	if len(v.Arcs) > 0 {
   260  		var match int
   261  		for _, a := range v.Arcs {
   262  			d := doms.Lookup(a.Label)
   263  			match |= t.findSubordinates(d, a, pickedDefault)
   264  		}
   265  
   266  		// This also skips embedded scalars if not all fields are removed. In
   267  		// this case we need to preserve the scalar to keep the type of the
   268  		// struct intact, which might as well be done by not removing the scalar
   269  		// type.
   270  		if match&yes == 0 || match&no != 0 {
   271  			return match
   272  		}
   273  	}
   274  
   275  	if !t.allowRemove(v) {
   276  		return no
   277  	}
   278  
   279  	switch v.BaseValue.(type) {
   280  	case *adt.StructMarker, *adt.ListMarker:
   281  		// Rely on previous processing of the Arcs and the fact that we take the
   282  		// default value to check dominator subsumption, meaning that we don't
   283  		// have to check additional optional constraints to pass subsumption.
   284  
   285  	default:
   286  		if !hasSubs {
   287  			return maybe
   288  		}
   289  
   290  		// This should normally not be necessary, as subsume should catch this.
   291  		// But as we already take the default value for doms, it doesn't hurt to
   292  		// do it.
   293  		v = v.Default()
   294  
   295  		// This is not necessary, but seems like it may result in more
   296  		// user-friendly semantics.
   297  		if v.IsErr() {
   298  			return no
   299  		}
   300  
   301  		// TODO: since we take v, instead of the unification of subordinate
   302  		// values, it should suffice to take equality here:
   303  		//    doms ⊑ subs  ==> doms == subs&doms
   304  		if err := subsume.Value(t.ctx, v, doms); err != nil {
   305  			return no
   306  		}
   307  	}
   308  
   309  	v.VisitLeafConjuncts(func(c adt.Conjunct) bool {
   310  		_, allowRemove := isDominator(c)
   311  		if !allowRemove && removable(c, v) {
   312  			t.markRemove(c)
   313  		}
   314  		return true
   315  	})
   316  
   317  	return yes
   318  }