internal/core/adt: fix matching of "_" label

The introduction of the wildcards in the API introduced
a regression that caused the string label "_" (which has
the feature label 0), to be incorrectly singled out.

The logic has now changed to reflect this. Note that without
the changed logic, TestAllow in cue/types_test.go will fail.

This also fixes printing of Selectors. Again, here "_" was
incorrectly singled out and would convert a string label to
a hidden label. This only seems to affect debug output.

Fixes #1454

Signed-off-by: Marcel van Lohuizen <mpvl@golang.org>

Change-Id: If08166862a677738af47937710774f6af5448953
Signed-off-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/530860
Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
diff --git a/cue/testdata/export/004.txtar b/cue/testdata/export/004.txtar
index 14a1c36..d201ad7 100644
--- a/cue/testdata/export/004.txtar
+++ b/cue/testdata/export/004.txtar
@@ -17,7 +17,7 @@
 {
   {
     $type: 3
-    _: int
+    "_": int
     "_foo": int
     _bar: int
   }
@@ -25,7 +25,7 @@
 -- out/eval --
 (struct){
   $type: (int){ 3 }
-  _: (int){ int }
+  "_": (int){ int }
   "_foo": (int){ int }
   _bar: (int){ int }
 }
diff --git a/cue/testdata/references/labels.txtar b/cue/testdata/references/labels.txtar
index a79c994..81655d7 100644
--- a/cue/testdata/references/labels.txtar
+++ b/cue/testdata/references/labels.txtar
@@ -50,6 +50,38 @@
     a: emptyLabel[""]
 }
 
+underscore: a: {
+    // Issue #1454
+    foo: #Foo
+    foo: "_": "bar"
+    #Foo: [=~""]: string
+}
+
+underscore: b: {
+    foo: #Foo
+    // TODO: we should probably disallow the hidden label `_` to avoid confusion.
+    foo: _: "bar"
+    #Foo: [=~""]: string
+}
+
+underscore: c: {
+    foo: "_": "any"
+    foo: [=~""]: string
+}
+
+underscore: d: {
+    bar: "_": "any"
+    #bar: [string]: string
+    bar: #bar
+}
+
+underscore: e: {
+    baz: "_h": "any"
+    #baz: [=~"_"]: string
+    baz: #baz
+}
+
+
 // TODO: support. Also not yet supported in old implementation.
 // c10: {
 // 	C=[string]: {
@@ -104,6 +136,41 @@
     "": (int){ 1 }
     a: (int){ 1 }
   }
+  underscore: (struct){
+    a: (struct){
+      foo: (#struct){
+        "_": (string){ "bar" }
+      }
+      #Foo: (#struct){
+      }
+    }
+    b: (struct){
+      foo: (#struct){
+        _: (string){ "bar" }
+      }
+      #Foo: (#struct){
+      }
+    }
+    c: (struct){
+      foo: (struct){
+        "_": (string){ "any" }
+      }
+    }
+    d: (struct){
+      bar: (#struct){
+        "_": (string){ "any" }
+      }
+      #bar: (#struct){
+      }
+    }
+    e: (struct){
+      baz: (#struct){
+        "_h": (string){ "any" }
+      }
+      #baz: (#struct){
+      }
+    }
+  }
 }
 -- out/compile --
 --- in.cue
@@ -188,4 +255,58 @@
     "": 1
     a: 〈1;emptyLabel〉[""]
   }
+  underscore: {
+    a: {
+      foo: 〈0;#Foo〉
+      foo: {
+        "_": "bar"
+      }
+      #Foo: {
+        [=~""]: string
+      }
+    }
+  }
+  underscore: {
+    b: {
+      foo: 〈0;#Foo〉
+      foo: {
+        _: "bar"
+      }
+      #Foo: {
+        [=~""]: string
+      }
+    }
+  }
+  underscore: {
+    c: {
+      foo: {
+        "_": "any"
+      }
+      foo: {
+        [=~""]: string
+      }
+    }
+  }
+  underscore: {
+    d: {
+      bar: {
+        "_": "any"
+      }
+      #bar: {
+        [string]: string
+      }
+      bar: 〈0;#bar〉
+    }
+  }
+  underscore: {
+    e: {
+      baz: {
+        "_h": "any"
+      }
+      #baz: {
+        [=~"_"]: string
+      }
+      baz: 〈0;#baz〉
+    }
+  }
 }
diff --git a/internal/core/adt/closed.go b/internal/core/adt/closed.go
index 5e9d5f3..4cf7f76 100644
--- a/internal/core/adt/closed.go
+++ b/internal/core/adt/closed.go
@@ -340,12 +340,10 @@
 		optionalTypes |= s.types
 	}
 
+	var str Value
 	if f.Index() == MaxIndex {
 		f = 0
-	}
-
-	var str Value
-	if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() && f > 0 {
+	} else if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() {
 		str = f.ToValue(ctx)
 	}
 
diff --git a/internal/core/adt/feature.go b/internal/core/adt/feature.go
index a099314..347ac2a 100644
--- a/internal/core/adt/feature.go
+++ b/internal/core/adt/feature.go
@@ -68,9 +68,6 @@
 // SelectorString reports the shortest string representation of f when used as a
 // selector.
 func (f Feature) SelectorString(index StringIndexer) string {
-	if f == 0 {
-		return "_"
-	}
 	x := f.safeIndex()
 	switch f.Typ() {
 	case IntLabel: