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 }