cue: separate closedness for lists and structs

Before embedded scalars, it was fine to "overload" IsClosed
for Lists and Structs. With embedded scalars, however,
whether embedded list is open or closed as a list and struct
are two orthogonal concepts. The API and code needs to
reflect this.

This also holds for API users. There is now a new "Allows"
method on Value to determine if a certain selector
is "fillable" on a Value. The special selectors
AnyString and AnyIndex have been added to allow querying
the possibility of setting a value.

Once closedness of definitions is handled properly
AnyDefinition should be added as well (note that hidden
labels are always allowed).

This deprecates the IsClosed method.

Remove the internal IsClosed method to ensure that a
decision made on what to use in all cases.

Fixes #879

Change-Id: I40334c8254435503bbd59a4f5d725ca415ff03b6
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9325
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/examples_test.go b/cue/examples_test.go
index 330234d..2ed3e4e 100644
--- a/cue/examples_test.go
+++ b/cue/examples_test.go
@@ -65,3 +65,74 @@
 	// 1 <nil>
 	// 2 <nil>
 }
+
+func ExampleValue_Allows() {
+	const file = `
+-- main.cue --
+a: [1, 2, ...int]
+
+b: #Point
+#Point: {
+	x:  int
+	y:  int
+	z?: int
+}
+
+c: [string]: int
+
+d: #C
+#C: [>"m"]: int
+`
+
+	v := load(file).Value()
+
+	a := v.LookupPath(cue.ParsePath("a"))
+	fmt.Println("a allows:")
+	fmt.Println("  index 4:       ", a.Allows(cue.Index(4)))
+	fmt.Println("  any index:     ", a.Allows(cue.AnyIndex))
+	fmt.Println("  any string:    ", a.Allows(cue.AnyString))
+
+	b := v.LookupPath(cue.ParsePath("b"))
+	fmt.Println("b allows:")
+	fmt.Println("  field x:       ", b.Allows(cue.Str("x")))
+	fmt.Println("  field z:       ", b.Allows(cue.Str("z")))
+	fmt.Println("  field foo:     ", b.Allows(cue.Str("foo")))
+	fmt.Println("  index 4:       ", b.Allows(cue.Index(4)))
+	fmt.Println("  any string:    ", b.Allows(cue.AnyString))
+
+	c := v.LookupPath(cue.ParsePath("c"))
+	fmt.Println("c allows:")
+	fmt.Println("  field z:       ", c.Allows(cue.Str("z")))
+	fmt.Println("  field foo:     ", c.Allows(cue.Str("foo")))
+	fmt.Println("  index 4:       ", c.Allows(cue.Index(4)))
+	fmt.Println("  any string:    ", c.Allows(cue.AnyString))
+
+	d := v.LookupPath(cue.ParsePath("d"))
+	fmt.Println("d allows:")
+	fmt.Println("  field z:       ", d.Allows(cue.Str("z")))
+	fmt.Println("  field foo:     ", d.Allows(cue.Str("foo")))
+	fmt.Println("  index 4:       ", d.Allows(cue.Index(4)))
+	fmt.Println("  any string:    ", d.Allows(cue.AnyString))
+
+	// Output:
+	// a allows:
+	//   index 4:        true
+	//   any index:      true
+	//   any string:     false
+	// b allows:
+	//   field x:        true
+	//   field z:        true
+	//   field foo:      false
+	//   index 4:        false
+	//   any string:     false
+	// c allows:
+	//   field z:        true
+	//   field foo:      true
+	//   index 4:        false
+	//   any string:     true
+	// d allows:
+	//   field z:        true
+	//   field foo:      false
+	//   index 4:        false
+	//   any string:     false
+}
diff --git a/cue/path.go b/cue/path.go
index 9a5074d..29edd0b 100644
--- a/cue/path.go
+++ b/cue/path.go
@@ -44,6 +44,32 @@
 	return sel.sel.kind() == adt.StringLabel
 }
 
+var (
+	// AnyField can be used to ask for any single label.
+	//
+	// In paths it is used to select constraints that apply to all elements.
+	// AnyField = anyField
+	anyField = Selector{sel: anySelector(adt.AnyLabel)}
+
+	// AnyDefinition can be used to ask for any definition.
+	//
+	// In paths it is used to select constraints that apply to all elements.
+	// AnyDefinition = anyDefinition
+	anyDefinition = Selector{sel: anySelector(adt.AnyDefinition)}
+
+	// AnyIndex can be used to ask for any index.
+	//
+	// In paths it is used to select constraints that apply to all elements.
+	AnyIndex = anyIndex
+	anyIndex = Selector{sel: anySelector(adt.AnyIndex)}
+
+	// AnyString can be used to ask for any regular string field.
+	//
+	// In paths it is used to select constraints that apply to all elements.
+	AnyString = anyLabel
+	anyLabel  = Selector{sel: anySelector(adt.AnyRegular)}
+)
+
 type selector interface {
 	String() string
 
@@ -355,6 +381,16 @@
 	return adt.Feature(s)
 }
 
+// an anySelector represents a wildcard option of a particular type.
+type anySelector adt.Feature
+
+func (s anySelector) String() string        { return "_" }
+func (s anySelector) kind() adt.FeatureType { return adt.Feature(s).Typ() }
+
+func (s anySelector) feature(r adt.Runtime) adt.Feature {
+	return adt.Feature(s)
+}
+
 // TODO: allow import paths to be represented?
 //
 // // ImportPath defines a lookup at the root of an instance. It must be the first
diff --git a/cue/path_test.go b/cue/path_test.go
index 388df11..b126438 100644
--- a/cue/path_test.go
+++ b/cue/path_test.go
@@ -103,7 +103,7 @@
 		path: ParsePath(`b[3T]`),
 		str:  "_|_",
 		err:  true,
-		out:  `_|_ // int label out of range (3000000000000 not >=0 and <= 268435455)`,
+		out:  `_|_ // int label out of range (3000000000000 not >=0 and <= 268435454)`,
 	}, {
 		path: ParsePath(`b[3.3]`),
 		str:  "_|_",
diff --git a/cue/testdata/benchmarks/deduparc.txtar b/cue/testdata/benchmarks/deduparc.txtar
index f0346e6..bd610cd 100644
--- a/cue/testdata/benchmarks/deduparc.txtar
+++ b/cue/testdata/benchmarks/deduparc.txtar
@@ -21,7 +21,7 @@
 foo: #Value
 -- out/eval --
 (struct){
-  #Value: (struct){ |((#struct){
+  #Value: (#struct){ |((#struct){
       type: (string){ "float" }
     }, (#struct){
       type: (string){ "string" }
diff --git a/cue/testdata/benchmarks/dedupelem.txtar b/cue/testdata/benchmarks/dedupelem.txtar
index ca1ed5b..2571931 100644
--- a/cue/testdata/benchmarks/dedupelem.txtar
+++ b/cue/testdata/benchmarks/dedupelem.txtar
@@ -27,7 +27,7 @@
 Result:
 (_|_){
   // [eval]
-  #Value: (struct){ |((#struct){
+  #Value: (#struct){ |((#struct){
       type: (string){ "int" }
     }, (#struct){
       type: (string){ "float" }
diff --git a/cue/testdata/builtins/closed.txtar b/cue/testdata/builtins/closed.txtar
index 714a6cf..e53e66d 100644
--- a/cue/testdata/builtins/closed.txtar
+++ b/cue/testdata/builtins/closed.txtar
@@ -78,7 +78,7 @@
   inDisjunctions: (struct){
     x: (struct){
       socket: (#struct){
-        string: (struct){ |((#struct){
+        string: (#struct){ |((#struct){
             a: (#struct){
               b: (bool){ true }
             }
@@ -99,7 +99,7 @@
           }) }
       }
       syslog: (#struct){
-        string: (struct){ |((#struct){
+        string: (#struct){ |((#struct){
             a: (#struct){
               b: (bool){ true }
             }
@@ -118,7 +118,7 @@
               }
             }
           }) }
-        xxx: (struct){ |((#struct){
+        xxx: (#struct){ |((#struct){
             a: (#struct){
               b: (bool){ true }
             }
diff --git a/cue/testdata/comprehensions/issue843.txtar b/cue/testdata/comprehensions/issue843.txtar
index 0a462fa..9353a3a 100644
--- a/cue/testdata/comprehensions/issue843.txtar
+++ b/cue/testdata/comprehensions/issue843.txtar
@@ -81,7 +81,7 @@
     #c: (list){
     }
   }
-  #o: (struct){ |((#struct){
+  #o: (#struct){ |((#struct){
       do: (string){ "f1" }
       as: (list){
       }
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index ddd552b..ad73c5a 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -675,7 +675,7 @@
     #ref: (#struct){
       ref: (string){ string }
     }
-    x: (struct){ |((#struct){
+    x: (#struct){ |((#struct){
         c: (#list){
           0: ((string|struct)){ |((string){ string }, (#struct){
               ref: (string){ string }
diff --git a/cue/testdata/definitions/036_closing_with_failed_optional.txtar b/cue/testdata/definitions/036_closing_with_failed_optional.txtar
index a5dd8bb..ea8e37d 100644
--- a/cue/testdata/definitions/036_closing_with_failed_optional.txtar
+++ b/cue/testdata/definitions/036_closing_with_failed_optional.txtar
@@ -109,7 +109,7 @@
   }
   #o2: (#struct){
   }
-  #d1: (struct){ |((#struct){
+  #d1: (#struct){ |((#struct){
       b: (int){ 4 }
     }, (#struct){
       c: (int){ 5 }
diff --git a/cue/testdata/definitions/embed.txtar b/cue/testdata/definitions/embed.txtar
index b1fb0be..f76002f 100644
--- a/cue/testdata/definitions/embed.txtar
+++ b/cue/testdata/definitions/embed.txtar
@@ -163,7 +163,7 @@
     }
   }
   reclose3: (struct){
-    #Step: (struct){ |((#struct){
+    #Step: (#struct){ |((#struct){
         Name: (string){ string }
         Something: (int){ int }
       }, (#struct){
diff --git a/cue/testdata/definitions/files.txtar b/cue/testdata/definitions/files.txtar
index a95a0e0..cb28e93 100644
--- a/cue/testdata/definitions/files.txtar
+++ b/cue/testdata/definitions/files.txtar
@@ -39,7 +39,7 @@
     ctermbg: (string){ "254" }
   }
   #Config: (#struct){
-    console: (struct){ |(*(#struct){
+    console: (#struct){ |(*(#struct){
         color: (string){ "light" }
         ctermbg: (string){ "254" }
       }, (#struct){
diff --git a/cue/testdata/definitions/issue342.txtar b/cue/testdata/definitions/issue342.txtar
index bfbc353..43c7ece 100644
--- a/cue/testdata/definitions/issue342.txtar
+++ b/cue/testdata/definitions/issue342.txtar
@@ -33,13 +33,13 @@
     #Simple: (#struct){
       ref: (string){ string }
     }
-    #Complex: (struct){ |((#struct){
+    #Complex: (#struct){ |((#struct){
         ref: (string){ string }
       }, (#struct){
         local: (string){ string }
       }) }
     var: (string){ "XXX" }
-    test_1: (struct){ |((#struct){
+    test_1: (#struct){ |((#struct){
         ref: (string){ string }
       }, (#struct){
         local: (string){ string }
diff --git a/cue/testdata/definitions/issue419.txtar b/cue/testdata/definitions/issue419.txtar
index e23c6f0..22d10f9 100644
--- a/cue/testdata/definitions/issue419.txtar
+++ b/cue/testdata/definitions/issue419.txtar
@@ -22,7 +22,7 @@
   #B: (#struct){
     b: (string){ string }
   }
-  #X: (struct){ |((#struct){
+  #X: (#struct){ |((#struct){
       a: (string){ string }
     }, (#struct){
       b: (string){ string }
diff --git a/cue/testdata/definitions/issue471.txtar b/cue/testdata/definitions/issue471.txtar
index 2baf609..e4625cc 100644
--- a/cue/testdata/definitions/issue471.txtar
+++ b/cue/testdata/definitions/issue471.txtar
@@ -24,7 +24,7 @@
 }
 -- out/eval --
 (struct){
-  #a: (struct){ |((#struct){
+  #a: (#struct){ |((#struct){
       name: (string){ string }
       age: (int){ int }
     }, (#struct){
diff --git a/cue/testdata/disjunctions/elimination.txtar b/cue/testdata/disjunctions/elimination.txtar
index 60bc35d..9b7ebf7 100644
--- a/cue/testdata/disjunctions/elimination.txtar
+++ b/cue/testdata/disjunctions/elimination.txtar
@@ -185,17 +185,17 @@
 -- out/eval --
 (struct){
   disambiguateClosed: (struct){
-    b: (struct){ |((#struct){
+    b: (#struct){ |((#struct){
         x: (bool){ true }
       }, (#struct){
         y: (bool){ true }
       }) }
-    a: (struct){ |((#struct){
+    a: (#struct){ |((#struct){
         x: (bool){ true }
       }, (#struct){
         y: (bool){ true }
       }) }
-    #Def: (struct){ |((#struct){
+    #Def: (#struct){ |((#struct){
         x: (bool){ true }
       }, (#struct){
         y: (bool){ true }
@@ -364,13 +364,13 @@
   preserveClosedness: (struct){
     small: (struct){
       p1: (struct){
-        #A: (struct){ |(*(#struct){
+        #A: (#struct){ |(*(#struct){
             a: (string){ string }
           }, (#struct){
             a: (string){ string }
             b: (int){ int }
           }) }
-        #B: (struct){ |(*(#struct){
+        #B: (#struct){ |(*(#struct){
           }, (#struct){
             b: (int){ int }
           }, (#struct){
@@ -381,13 +381,13 @@
           }) }
       }
       p2: (struct){
-        #A: (struct){ |(*(#struct){
+        #A: (#struct){ |(*(#struct){
             a: (string){ string }
           }, (#struct){
             a: (string){ string }
             b: (int){ int }
           }) }
-        #B: (struct){ |(*(#struct){
+        #B: (#struct){ |(*(#struct){
           }, (#struct){
             a: (string){ string }
             b: (int){ int }
@@ -400,7 +400,7 @@
     }
     medium: (struct){
       p1: (struct){
-        #A: (struct){ |(*(#struct){
+        #A: (#struct){ |(*(#struct){
             a: (string){ string }
           }, (#struct){
             a: (string){ string }
@@ -409,7 +409,7 @@
             a: (string){ string }
             d: (string){ string }
           }) }
-        #B: (struct){ |(*(#struct){
+        #B: (#struct){ |(*(#struct){
           }, (#struct){
             c: (int){ int }
           }, (#struct){
@@ -433,7 +433,7 @@
           }) }
       }
       p2: (struct){
-        #A: (struct){ |(*(#struct){
+        #A: (#struct){ |(*(#struct){
             a: (string){ string }
           }, (#struct){
             a: (string){ string }
@@ -442,7 +442,7 @@
             a: (string){ string }
             d: (string){ string }
           }) }
-        #B: (struct){ |(*(#struct){
+        #B: (#struct){ |(*(#struct){
           }, (#struct){
             a: (string){ string }
             c: (int){ int }
@@ -466,7 +466,7 @@
           }) }
       }
       p3: (struct){
-        #A: (struct){ |(*(#struct){
+        #A: (#struct){ |(*(#struct){
             a: (string){ string }
           }, (#struct){
             a: (string){ string }
@@ -475,7 +475,7 @@
             a: (string){ string }
             d: (string){ string }
           }) }
-        #B: (struct){ |(*(#struct){
+        #B: (#struct){ |(*(#struct){
           }, (#struct){
             a: (string){ string }
             c: (int){ int }
diff --git a/cue/testdata/disjunctions/embed.txtar b/cue/testdata/disjunctions/embed.txtar
index 3a98652..ac238c2 100644
--- a/cue/testdata/disjunctions/embed.txtar
+++ b/cue/testdata/disjunctions/embed.txtar
@@ -111,7 +111,7 @@
   }
   defaultsMulti: (struct){
     a: (struct){
-      #def: (struct){ |(*(#struct){
+      #def: (#struct){ |(*(#struct){
         }, (#struct){
           c: (string){ string }
         }, (#struct){
@@ -133,7 +133,7 @@
           b: (string){ string }
           d: (string){ string }
         }) }
-      a: (struct){ |(*(#struct){
+      a: (#struct){ |(*(#struct){
           a: (string){ "foo" }
         }, (#struct){
           a: (string){ "foo" }
@@ -144,7 +144,7 @@
         }) }
     }
     b: (struct){
-      #def: (struct){ |(*(#struct){
+      #def: (#struct){ |(*(#struct){
         }, (#struct){
           d: (string){ string }
         }, (#struct){
@@ -207,7 +207,7 @@
           d: (string){ string }
           e: (string){ string }
         }) }
-      a: (struct){ |(*(#struct){
+      a: (#struct){ |(*(#struct){
           a: (string){ "foo" }
           e: (string){ "bar" }
         }, (#struct){
diff --git a/cue/testdata/disjunctions/errors.txtar b/cue/testdata/disjunctions/errors.txtar
index 7fc79a5..1a1ba71 100644
--- a/cue/testdata/disjunctions/errors.txtar
+++ b/cue/testdata/disjunctions/errors.txtar
@@ -82,12 +82,12 @@
       }
       error: (string){ string }
     }
-    #Output: (struct){ |((#struct){
+    #Output: (#struct){ |((#struct){
         result: (_){ _ }
       }, (#struct){
         error: (string){ string }
       }) }
-    #DecodeOutput: (struct){ |((#struct){
+    #DecodeOutput: (#struct){ |((#struct){
         result: (list){
         }
       }, (#struct){
@@ -96,7 +96,7 @@
   }
   issue516: (_|_){
     // [eval]
-    #Def: (struct){ |((#struct){
+    #Def: (#struct){ |((#struct){
         match: (#struct){
           metrics: (#struct){
             string: (#struct){
diff --git a/cue/testdata/eval/closed_disjunction.txtar b/cue/testdata/eval/closed_disjunction.txtar
index 0630182..397f6e8 100644
--- a/cue/testdata/eval/closed_disjunction.txtar
+++ b/cue/testdata/eval/closed_disjunction.txtar
@@ -32,7 +32,7 @@
 Result:
 (_|_){
   // [eval]
-  #A: (struct){ |(*(#struct){
+  #A: (#struct){ |(*(#struct){
     }, (#struct){
     }, (#struct){
     }, (#struct){
diff --git a/cue/testdata/eval/disjunctions.txtar b/cue/testdata/eval/disjunctions.txtar
index 5027d2b..7a550de 100644
--- a/cue/testdata/eval/disjunctions.txtar
+++ b/cue/testdata/eval/disjunctions.txtar
@@ -286,7 +286,7 @@
       x: (string){ string }
     }
     #C: (#struct){
-      b: (struct){ |((#struct){
+      b: (#struct){ |((#struct){
           type: (string){ "a" }
           x: (string){ "" }
         }, (#struct){
diff --git a/cue/testdata/eval/issue353.txtar b/cue/testdata/eval/issue353.txtar
index 9b26d24..906d662 100644
--- a/cue/testdata/eval/issue353.txtar
+++ b/cue/testdata/eval/issue353.txtar
@@ -13,12 +13,12 @@
 }
 -- out/eval --
 (struct){
-  e: (struct){ |((#struct){
+  e: (#struct){ |((#struct){
       a: (string){ "hello" }
     }, (#struct){
       a: (string){ "hello" }
     }) }
-  #Example: (struct){ |((#struct){
+  #Example: (#struct){ |((#struct){
       a: (string){ string }
     }, (#struct){
       a: (string){ string }
diff --git a/cue/testdata/fulleval/040.txtar b/cue/testdata/fulleval/040.txtar
index 6d1a247..e1ceae1 100644
--- a/cue/testdata/fulleval/040.txtar
+++ b/cue/testdata/fulleval/040.txtar
@@ -67,7 +67,7 @@
 }
 -- out/eval --
 (struct){
-  #Task: (struct){ |((#struct){
+  #Task: (#struct){ |((#struct){
       op: (string){ "pull" }
       tag: (string){ |(*(string){ "latest" }, (string){ string }) }
       refToTag: (string){ |(*(string){ "latest" }, (string){ string }) }
diff --git a/cue/testdata/resolve/030_definitions_with_disjunctions.txtar b/cue/testdata/resolve/030_definitions_with_disjunctions.txtar
index 5c6fe2b..ac29448 100644
--- a/cue/testdata/resolve/030_definitions_with_disjunctions.txtar
+++ b/cue/testdata/resolve/030_definitions_with_disjunctions.txtar
@@ -71,7 +71,7 @@
 Result:
 (_|_){
   // [eval]
-  #Foo: (struct){ |((#struct){
+  #Foo: (#struct){ |((#struct){
       field: (int){ int }
       a: (int){ 1 }
     }, (#struct){
diff --git a/cue/testdata/resolve/031_definitions_with_disjunctions_recurisive.txtar b/cue/testdata/resolve/031_definitions_with_disjunctions_recurisive.txtar
index 42842f6..ca75aff 100644
--- a/cue/testdata/resolve/031_definitions_with_disjunctions_recurisive.txtar
+++ b/cue/testdata/resolve/031_definitions_with_disjunctions_recurisive.txtar
@@ -52,7 +52,7 @@
 -- out/eval --
 (struct){
   #Foo: (#struct){
-    x: (struct){ |((#struct){
+    x: (#struct){ |((#struct){
         field: (int){ int }
         c: (int){ 3 }
         a: (int){ 1 }
diff --git a/cue/testdata/resolve/033_top-level_definition_with_struct_and_disjunction.txtar b/cue/testdata/resolve/033_top-level_definition_with_struct_and_disjunction.txtar
index fd4b8f8..3253f8d 100644
--- a/cue/testdata/resolve/033_top-level_definition_with_struct_and_disjunction.txtar
+++ b/cue/testdata/resolve/033_top-level_definition_with_struct_and_disjunction.txtar
@@ -52,7 +52,7 @@
 }
 -- out/eval --
 (struct){
-  #def: (struct){ |((#struct){
+  #def: (#struct){ |((#struct){
       Type: (string){ "B" }
       Text: (string){ string }
       Size: (int){ 0 }
diff --git a/cue/types.go b/cue/types.go
index da5e1e2..8bb45fe 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -848,7 +848,7 @@
 	if !v.v.IsConcrete() {
 		return BottomKind
 	}
-	if v.IncompleteKind() == adt.ListKind && !v.IsClosed() {
+	if v.IncompleteKind() == adt.ListKind && !v.v.IsClosedList() {
 		return BottomKind
 	}
 	return c.Kind()
@@ -1086,11 +1086,23 @@
 
 // IsClosed reports whether a list of struct is closed. It reports false when
 // when the value is not a list or struct.
+//
+// Deprecated: use Allows and Kind/IncompleteKind.
 func (v Value) IsClosed() bool {
 	if v.v == nil {
 		return false
 	}
-	return v.v.IsClosed(v.ctx().opCtx)
+	return v.v.IsClosedList() || v.v.IsClosedStruct()
+}
+
+// Allows reports whether a field with the given selector could be added to v.
+//
+// Allows does not take into account validators like list.MaxItems(4). This may
+// change in the future.
+func (v Value) Allows(sel Selector) bool {
+	c := v.ctx().opCtx
+	f := sel.sel.feature(c)
+	return v.v.Accept(c, f)
 }
 
 // IsConcrete reports whether the current value is a concrete scalar value
@@ -1107,7 +1119,7 @@
 	if !adt.IsConcrete(v.v) {
 		return false
 	}
-	if v.IncompleteKind() == adt.ListKind && !v.IsClosed() {
+	if v.IncompleteKind() == adt.ListKind && !v.v.IsClosedList() {
 		return false
 	}
 	return true
@@ -1168,10 +1180,9 @@
 		switch x := v.eval(v.ctx()).(type) {
 		case *adt.Vertex:
 			if x.IsList() {
-				ctx := v.ctx()
 				n := &adt.Num{K: adt.IntKind}
 				n.X.SetInt64(int64(len(x.Elems())))
-				if x.IsClosed(ctx.opCtx) {
+				if x.IsClosedList() {
 					return remakeFinal(v, nil, n)
 				}
 				// Note: this HAS to be a Conjunction value and cannot be
@@ -1215,38 +1226,6 @@
 	return makeValue(v.idx, x), true
 }
 
-// // BulkOptionals returns all bulk optional fields as key-value pairs.
-// // See also Elem and Template.
-// func (v Value) BulkOptionals() [][2]Value {
-// 	x, ok := v.path.cache.(*structLit)
-// 	if !ok {
-// 		return nil
-// 	}
-// 	return v.appendBulk(nil, x.optionals)
-// }
-
-// func (v Value) appendBulk(a [][2]Value, x *optionals) [][2]Value {
-// 	if x == nil {
-// 		return a
-// 	}
-// 	a = v.appendBulk(a, x.left)
-// 	a = v.appendBulk(a, x.right)
-// 	for _, set := range x.fields {
-// 		if set.key != nil {
-// 			ctx := v.ctx()
-// 			fn, ok := ctx.manifest(set.value).(*lambdaExpr)
-// 			if !ok {
-// 				// create error
-// 				continue
-// 			}
-// 			x := fn.call(ctx, set.value, &basicType{K: stringKind})
-
-// 			a = append(a, [2]Value{v.makeElem(set.key), v.makeElem(x)})
-// 		}
-// 	}
-// 	return a
-// }
-
 // List creates an iterator over the values of a list or reports an error if
 // v is not a list.
 func (v Value) List() (Iterator, error) {
@@ -1793,7 +1772,7 @@
 }
 
 func allowed(ctx *adt.OpContext, parent, n *adt.Vertex) *adt.Bottom {
-	if !parent.IsClosed(ctx) && !isDef(parent) {
+	if !parent.IsClosedList() && !parent.IsClosedStruct() && !isDef(parent) {
 		return nil
 	}
 
diff --git a/cue/types_test.go b/cue/types_test.go
index 4e74180..51d84a2 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -1146,6 +1146,245 @@
 	}
 }
 
+func TestAllows(t *testing.T) {
+	r := &Runtime{}
+
+	testCases := []struct {
+		desc  string
+		in    string
+		sel   Selector
+		allow bool
+	}{{
+		desc: "allow new field in open struct",
+		in: `
+		x: {
+			a: int
+		}
+		`,
+		sel:   Str("b"),
+		allow: true,
+	}, {
+		desc: "disallow new field in definition",
+		in: `
+		x: #Def
+		#Def: {
+			a: int
+		}
+		`,
+		sel: Str("b"),
+	}, {
+		desc: "disallow new field in explicitly closed struct",
+		in: `
+		x: close({
+			a: int
+		})
+		`,
+		sel: Str("b"),
+	}, {
+		desc: "allow index in open list",
+		in: `
+		x: [...int]
+		`,
+		sel:   Index(100),
+		allow: true,
+	}, {
+		desc: "disallow index in closed list",
+		in: `
+		x: []
+		`,
+		sel: Index(0),
+	}, {
+		desc: "allow existing index in closed list",
+		in: `
+		x: [1]
+		`,
+		sel:   Index(0),
+		allow: true,
+	}, {
+		desc: "definition in non-def closed list",
+		in: `
+		x: [1]
+		`,
+		sel:   Def("#foo"),
+		allow: true,
+	}, {
+		// TODO(disallow)
+		desc: "definition in def open list",
+		in: `
+		x: #Def
+		x: [1]
+		#Def: [...int]
+		`,
+		sel:   Def("#foo"),
+		allow: true,
+	}, {
+		desc: "field in def open list",
+		in: `
+		x: #Def
+		x: [1]
+		#Def: [...int]
+		`,
+		sel: Str("foo"),
+	}, {
+		desc: "definition in open scalar",
+		in: `
+		x: 1
+		`,
+		sel:   Def("#foo"),
+		allow: true,
+	}, {
+		desc: "field in scalar",
+		in: `
+		x: #Def
+		x: 1
+		#Def: int
+		`,
+		sel: Str("foo"),
+	}, {
+		desc: "any index in closed list",
+		in: `
+		x: [1]
+		`,
+		sel: AnyIndex,
+	}, {
+		desc: "any index in open list",
+		in: `
+		x: [...int]
+			`,
+		sel:   AnyIndex,
+		allow: true,
+	}, {
+		desc: "definition in open scalar",
+		in: `
+		x: 1
+		`,
+		sel:   anyDefinition,
+		allow: true,
+	}, {
+		desc: "field in open scalar",
+		in: `
+			x: 1
+			`,
+		sel: AnyString,
+
+		// TODO(v0.6.0)
+		// }, {
+		// 	desc: "definition in closed scalar",
+		// 	in: `
+		// 	x: #Def
+		// 	x: 1
+		// 	#Def: int
+		// 	`,
+		// 	sel:   AnyDefinition,
+		// 	allow: true,
+	}, {
+		desc: "allow field in any",
+		in: `
+			x: _
+			`,
+		sel:   AnyString,
+		allow: true,
+	}, {
+		desc: "allow index in any",
+		in: `
+		x: _
+		`,
+		sel:   AnyIndex,
+		allow: true,
+	}, {
+		desc: "allow index in disjunction",
+		in: `
+		x: [...int] | 1
+		`,
+		sel:   AnyIndex,
+		allow: true,
+	}, {
+		desc: "allow index in disjunction",
+		in: `
+		x: [] | [...int]
+			`,
+		sel:   AnyIndex,
+		allow: true,
+	}, {
+		desc: "disallow index in disjunction",
+		in: `
+		x: [1, 2] | [3, 2]
+		`,
+		sel: AnyIndex,
+	}, {
+		desc: "disallow index in non-list disjunction",
+		in: `
+		x: "foo" | 1
+		`,
+		sel: AnyIndex,
+	}, {
+		desc: "allow label in disjunction",
+		in: `
+		x: {} | 1
+		`,
+		sel:   AnyString,
+		allow: true,
+	}, {
+		desc: "allow label in disjunction",
+		in: `
+		x: #Def
+		#Def: { a: 1 } | { b: 1, ... }
+		`,
+		sel:   AnyString,
+		allow: true,
+	}, {
+		desc: "disallow label in disjunction",
+		in: `
+		x: #Def
+		#Def: { a: 1 } | { b: 1 }
+		`,
+		sel: AnyString,
+	}, {
+		desc: "pattern constraint",
+		in: `
+		x: #PC
+		#PC: [>"m"]: int
+		`,
+		sel: Str(""),
+	}, {
+		desc: "pattern constraint",
+		in: `
+		x: #PC
+		#PC: [>"m"]: int
+		`,
+		sel:   Str("z"),
+		allow: true,
+	}, {
+		desc: "any in pattern constraint",
+		in: `
+		x: #PC
+		#PC: [>"m"]: int
+		`,
+		sel: AnyString,
+	}, {
+		desc: "any in pattern constraint",
+		in: `
+		x: #PC
+		#PC: [>" "]: int
+		`,
+		sel: AnyString,
+	}}
+
+	path := ParsePath("x")
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			v := compileT(t, r, tc.in).Value()
+			v = v.LookupPath(path)
+
+			got := v.Allows(tc.sel)
+			if got != tc.allow {
+				t.Errorf("got %v; want %v", got, tc.allow)
+			}
+		})
+	}
+}
+
 func TestFillFloat(t *testing.T) {
 	// This tests panics for issue #749
 
diff --git a/internal/core/adt/closed.go b/internal/core/adt/closed.go
index 6633517..39a00fa 100644
--- a/internal/core/adt/closed.go
+++ b/internal/core/adt/closed.go
@@ -325,8 +325,12 @@
 		optionalTypes |= s.types
 	}
 
+	if f.Index() == MaxIndex {
+		f = 0
+	}
+
 	var str Value
-	if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() {
+	if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() && f > 0 {
 		str = f.ToValue(ctx)
 	}
 
@@ -472,10 +476,7 @@
 	errs := ctx.errs
 	defer func() { ctx.errs = errs }()
 
-	if len(o.Dynamic) > 0 && f.IsString() {
-		if label == nil && f.IsString() {
-			label = f.ToValue(ctx)
-		}
+	if len(o.Dynamic) > 0 && f.IsString() && label != nil {
 		for _, b := range o.Dynamic {
 			v := env.evalCached(ctx, b.Key)
 			s, ok := v.(*String)
diff --git a/internal/core/adt/closed2.go b/internal/core/adt/closed2.go
index f25c428..23606e5 100644
--- a/internal/core/adt/closed2.go
+++ b/internal/core/adt/closed2.go
@@ -26,7 +26,7 @@
 // subtree into the parent node using InsertSubtree. If not, the conjuncts can
 // just be inserted at the current ID.
 func isComplexStruct(ctx *OpContext, v *Vertex) bool {
-	return v.IsClosed(ctx)
+	return v.IsClosedStruct()
 }
 
 // TODO: cleanup code and error messages. Reduce duplication in some related
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index e1d96df..c6bd023 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -502,10 +502,14 @@
 func (v *Vertex) Kind() Kind {
 	// This is possible when evaluating comprehensions. It is potentially
 	// not known at this time what the type is.
-	if v.BaseValue == nil {
+	switch {
+	case v.state != nil:
+		return v.state.kind
+	case v.BaseValue == nil:
 		return TopKind
+	default:
+		return v.BaseValue.Kind()
 	}
-	return v.BaseValue.Kind()
 }
 
 func (v *Vertex) OptionalTypes() OptionalType {
@@ -531,33 +535,61 @@
 	return ok || (!required && !v.Closed)
 }
 
-func (v *Vertex) IsClosed(ctx *OpContext) bool {
+func (v *Vertex) IsClosedStruct() bool {
 	switch x := v.BaseValue.(type) {
-	case *ListMarker:
-		return !x.IsOpen
+	default:
+		return false
 
 	case *StructMarker:
 		if x.NeedClose {
 			return true
 		}
-		return v.Closed || isClosed(v)
+
+	case *Disjunction:
+	}
+	return v.Closed || isClosed(v)
+}
+
+func (v *Vertex) IsClosedList() bool {
+	if x, ok := v.BaseValue.(*ListMarker); ok {
+		return !x.IsOpen
 	}
 	return false
 }
 
 // TODO: return error instead of boolean? (or at least have version that does.)
 func (v *Vertex) Accept(ctx *OpContext, f Feature) bool {
-	if v.IsList() {
-		if f.IsInt() {
+	if f.IsInt() {
+		switch x := v.BaseValue.(type) {
+		case *ListMarker:
 			// TODO(perf): use precomputed length.
 			if f.Index() < len(v.Elems()) {
 				return true
 			}
+			return !v.IsClosedList()
+
+		case *Disjunction:
+			for _, v := range x.Values {
+				if v.Accept(ctx, f) {
+					return true
+				}
+			}
+			return false
+
+		default:
+			return v.Kind()&ListKind != 0
 		}
-		return !v.IsClosed(ctx)
 	}
 
-	if !f.IsString() || !v.IsClosed(ctx) || v.Lookup(f) != nil {
+	if k := v.Kind(); k&StructKind == 0 && f.IsString() && f != AnyLabel {
+		// If the value is bottom, we may not really know if this used to
+		// be a struct.
+		if k != BottomKind || len(v.Structs) == 0 {
+			return false
+		}
+	}
+
+	if f.IsHidden() || !v.IsClosedStruct() || v.Lookup(f) != nil {
 		return true
 	}
 
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index ce4fcc8..8163714 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -729,6 +729,7 @@
 	case *StructMarker:
 		if l.Typ() == IntLabel {
 			c.addErrf(0, pos, "invalid struct selector %s (type int)", l)
+			return nil
 		}
 
 	case *ListMarker:
diff --git a/internal/core/adt/equality.go b/internal/core/adt/equality.go
index 9f2d7ad..8203cfc 100644
--- a/internal/core/adt/equality.go
+++ b/internal/core/adt/equality.go
@@ -58,7 +58,7 @@
 
 	// TODO: this really should be subsumption.
 	if flags != 0 {
-		if x.IsClosed(ctx) != y.IsClosed(ctx) {
+		if x.IsClosedStruct() != y.IsClosedStruct() {
 			return false
 		}
 		if !equalClosed(ctx, x, y, flags) {
diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go
index e74d1ac..d2086cb 100644
--- a/internal/core/adt/eval.go
+++ b/internal/core/adt/eval.go
@@ -1994,7 +1994,7 @@
 		oneOfTheLists = l
 
 		elems := l.Elems()
-		isClosed := l.IsClosed(c)
+		isClosed := l.IsClosedList()
 
 		switch {
 		case len(elems) < max:
@@ -2096,7 +2096,7 @@
 	// add additionalItem values to list and construct optionals.
 	elems := n.node.Elems()
 	for _, l := range n.vLists {
-		if !l.IsClosed(c) {
+		if !l.IsClosedList() {
 			continue
 		}
 
diff --git a/internal/core/adt/feature.go b/internal/core/adt/feature.go
index bca9e62..d17a911 100644
--- a/internal/core/adt/feature.go
+++ b/internal/core/adt/feature.go
@@ -33,11 +33,24 @@
 // TODO: create labels such that list are sorted first (or last with index.)
 
 // InvalidLabel is an encoding of an erroneous label.
-const InvalidLabel Feature = 0x7 // 0xb111
+const (
+	InvalidLabel Feature = 0x7 // 0xb111
 
-// MaxIndex indicates the maximum number of unique strings that are used for
-// labeles within this CUE implementation.
-const MaxIndex int64 = 1<<28 - 1
+	// MaxIndex indicates the maximum number of unique strings that are used for
+	// labeles within this CUE implementation.
+	MaxIndex = 1<<28 - 1
+)
+
+// These labels can be used for wildcard queries.
+var (
+	// AnyLabel represents any label or index.
+	AnyLabel Feature = 0
+
+	AnyDefinition Feature = makeLabel(MaxIndex, DefinitionLabel)
+	AnyHidden     Feature = makeLabel(MaxIndex, HiddenLabel)
+	AnyRegular    Feature = makeLabel(MaxIndex, StringLabel)
+	AnyIndex      Feature = makeLabel(MaxIndex, IntLabel)
+)
 
 // A StringIndexer coverts strings to and from an index that is unique for a
 // given string.
@@ -58,12 +71,12 @@
 	if f == 0 {
 		return "_"
 	}
-	x := f.Index()
+	x := f.safeIndex()
 	switch f.Typ() {
 	case IntLabel:
 		return strconv.Itoa(int(x))
 	case StringLabel:
-		s := index.IndexToString(int64(x))
+		s := index.IndexToString(x)
 		if ast.IsValidIdent(s) && !internal.IsDefOrHidden(s) {
 			return s
 		}
@@ -76,7 +89,7 @@
 // IdentString reports the identifier of f. The result is undefined if f
 // is not an identifier label.
 func (f Feature) IdentString(index StringIndexer) string {
-	s := index.IndexToString(int64(f.Index()))
+	s := index.IndexToString(f.safeIndex())
 	if f.IsHidden() {
 		if p := strings.IndexByte(s, '\x00'); p >= 0 {
 			s = s[:p]
@@ -92,7 +105,7 @@
 	if !f.IsHidden() {
 		return ""
 	}
-	s := index.IndexToString(int64(f.Index()))
+	s := index.IndexToString(f.safeIndex())
 	if p := strings.IndexByte(s, '\x00'); p >= 0 {
 		return s[p+1:]
 	}
@@ -104,8 +117,8 @@
 	if !f.IsString() {
 		panic("not a string label")
 	}
-	x := f.Index()
-	return index.IndexToString(int64(x))
+	x := f.safeIndex()
+	return index.IndexToString(x)
 }
 
 // ToValue converts a label to a value, which will be a Num for integer labels
@@ -117,8 +130,8 @@
 	if f.IsInt() {
 		return ctx.NewInt64(int64(f.Index()))
 	}
-	x := f.Index()
-	str := ctx.IndexToString(int64(x))
+	x := f.safeIndex()
+	str := ctx.IndexToString(x)
 	return ctx.NewString(str)
 }
 
@@ -228,18 +241,22 @@
 
 // MakeLabel creates a label. It reports an error if the index is out of range.
 func MakeLabel(src ast.Node, index int64, f FeatureType) (Feature, errors.Error) {
-	if 0 > index || index > MaxIndex {
+	if 0 > index || index > MaxIndex-1 {
 		p := token.NoPos
 		if src != nil {
 			p = src.Pos()
 		}
 		return InvalidLabel,
 			errors.Newf(p, "int label out of range (%d not >=0 and <= %d)",
-				index, MaxIndex)
+				index, MaxIndex-1)
 	}
 	return Feature(index)<<indexShift | Feature(f), nil
 }
 
+func makeLabel(index int64, f FeatureType) Feature {
+	return Feature(index)<<indexShift | Feature(f)
+}
+
 // A FeatureType indicates the type of label.
 type FeatureType int8
 
@@ -301,7 +318,18 @@
 }
 
 // Index reports the abstract index associated with f.
-func (f Feature) Index() int { return int(f >> indexShift) }
+func (f Feature) Index() int {
+	return int(f >> indexShift)
+}
+
+// SafeIndex reports the abstract index associated with f, setting MaxIndex to 0.
+func (f Feature) safeIndex() int64 {
+	x := int(f >> indexShift)
+	if x == MaxIndex {
+		x = 0 // Safety, MaxIndex means any
+	}
+	return int64(x)
+}
 
 // TODO: should let declarations be implemented as fields?
 // func (f Feature) isLet() bool  { return f.typ() == letLabel }
diff --git a/internal/core/adt/optional.go b/internal/core/adt/optional.go
index daac4e8..bcb8e65 100644
--- a/internal/core/adt/optional.go
+++ b/internal/core/adt/optional.go
@@ -104,7 +104,7 @@
 		switch x.Kind() {
 		case StringKind:
 			if label == nil {
-				label = f.ToValue(c)
+				return false
 			}
 			str := label.(*String).Str
 			return x.validateStr(c, str)
@@ -114,12 +114,13 @@
 		}
 	}
 
+	if label == nil {
+		return false
+	}
+
 	n := Vertex{}
 	m := MakeRootConjunct(env, v)
 	n.AddConjunct(m)
-	if label == nil {
-		label = f.ToValue(c)
-	}
 	n.AddConjunct(MakeRootConjunct(m.Env, label))
 
 	c.inConstraint++
diff --git a/internal/core/compile/builtin.go b/internal/core/compile/builtin.go
index e65f806..feb7ade 100644
--- a/internal/core/compile/builtin.go
+++ b/internal/core/compile/builtin.go
@@ -85,7 +85,7 @@
 		if !ok {
 			return c.NewErrf("struct argument must be concrete")
 		}
-		if s.IsClosed(c) {
+		if s.IsClosedStruct() {
 			return s
 		}
 		v := *s
diff --git a/internal/core/debug/debug.go b/internal/core/debug/debug.go
index 125028c..997a3dc 100644
--- a/internal/core/debug/debug.go
+++ b/internal/core/debug/debug.go
@@ -158,7 +158,7 @@
 		kindStr := kind.String()
 
 		// TODO: replace with showing full closedness data.
-		if x.IsClosed(nil) {
+		if x.IsClosedList() || x.IsClosedStruct() {
 			if kind == adt.ListKind || kind == adt.StructKind {
 				kindStr = "#" + kindStr
 			}
diff --git a/internal/core/export/expr.go b/internal/core/export/expr.go
index bcb95f0..4af83ac 100644
--- a/internal/core/export/expr.go
+++ b/internal/core/export/expr.go
@@ -201,7 +201,7 @@
 	}
 	if e.hasEllipsis {
 		s.Elts = append(s.Elts, &ast.Ellipsis{})
-	} else if src != nil && src.IsClosed(e.ctx) && e.inDefinition == 0 {
+	} else if src != nil && src.IsClosedStruct() && e.inDefinition == 0 {
 		return ast.NewCall(ast.NewIdent("close"), s)
 	}
 
@@ -302,7 +302,7 @@
 				for _, x := range v.Elems() {
 					a = append(a, e.expr(x))
 				}
-				if !v.IsClosed(e.ctx) {
+				if !v.IsClosedList() {
 					v := &adt.Vertex{}
 					v.MatchAndInsert(e.ctx, v)
 					a = append(a, &ast.Ellipsis{Type: e.expr(v)})
diff --git a/internal/core/subsume/vertex.go b/internal/core/subsume/vertex.go
index d8ccd10..3d183f8 100644
--- a/internal/core/subsume/vertex.go
+++ b/internal/core/subsume/vertex.go
@@ -81,11 +81,11 @@
 		panic(fmt.Sprintf("unexpected type %T", v))
 	}
 
-	xClosed := x.IsClosed(ctx) && !s.IgnoreClosedness
+	xClosed := x.IsClosedStruct() && !s.IgnoreClosedness
 	// TODO: this should not close for taking defaults. Do a more principled
 	// makeover of this package before making it public, though.
 	yClosed := s.Final || s.Defaults ||
-		(y.IsClosed(ctx) && !s.IgnoreClosedness)
+		(y.IsClosedStruct() && !s.IgnoreClosedness)
 
 	if xClosed && !yClosed && !final {
 		return false
@@ -221,7 +221,7 @@
 func (s *subsumer) listVertices(x, y *adt.Vertex) bool {
 	ctx := s.ctx
 
-	if !y.IsData() && x.IsClosed(ctx) && !y.IsClosed(ctx) {
+	if !y.IsData() && x.IsClosedList() && !y.IsClosedList() {
 		return false
 	}
 
@@ -232,7 +232,7 @@
 	case len(xElems) == len(yElems):
 	case len(xElems) > len(yElems):
 		return false
-	case x.IsClosed(ctx):
+	case x.IsClosedList():
 		return false
 	default:
 		a := &adt.Vertex{Label: 0}
@@ -246,7 +246,7 @@
 			}
 		}
 
-		if !y.IsClosed(ctx) {
+		if !y.IsClosedList() {
 			b := &adt.Vertex{Label: adt.InvalidLabel}
 			y.MatchAndInsert(ctx, b)
 			b.Finalize(ctx)