cmd/compile/internal/syntax: parser to accept ~x as unary expression

Accept ~x as ordinary unary expression in the parser but recognize
such expressions as invalid in the type checker.

This change opens the door to recognizing complex type constraint
literals such as `*E|~int` in `[P *E|~int]` and parse them correctly
instead of reporting a parse error because `P*E|~int` syntactically
looks like an incorrect array length expression (binary expression
where the RHS of | is an invalid unary expression ~int).

As a result, the parser is more forgiving with expressions but the
type checker will reject invalid uses as before.

We could pass extra information into the binary/unary expression
parse functions to prevent the use of ~ in invalid situations but
it doesn't seem worth the trouble. In fact it may be advantageous
to allow a more liberal expression syntax especially in the presence
of errors (better parser synchronization after an error).

Preparation for fixing #49482.

Change-Id: I119e8bd9445dfa6460fcd7e0658e3554a34b2769
Reviewed-on: https://go-review.googlesource.com/c/go/+/402255
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
diff --git a/src/cmd/compile/internal/syntax/parser.go b/src/cmd/compile/internal/syntax/parser.go
index fe1c76e..a89dcfa 100644
--- a/src/cmd/compile/internal/syntax/parser.go
+++ b/src/cmd/compile/internal/syntax/parser.go
@@ -827,7 +827,7 @@
 	switch p.tok {
 	case _Operator, _Star:
 		switch p.op {
-		case Mul, Add, Sub, Not, Xor:
+		case Mul, Add, Sub, Not, Xor, Tilde:
 			x := new(Operation)
 			x.pos = p.pos()
 			x.Op = p.op
diff --git a/src/cmd/compile/internal/syntax/testdata/typeset.go b/src/cmd/compile/internal/syntax/testdata/typeset.go
index 19b74f2..fe5c3f4 100644
--- a/src/cmd/compile/internal/syntax/testdata/typeset.go
+++ b/src/cmd/compile/internal/syntax/testdata/typeset.go
@@ -65,15 +65,17 @@
 
 // Single-expression type parameter lists and those that don't start
 // with a (type parameter) name are considered array sizes.
-// The term must be a valid expression (it could be a type - and then
-// a type-checker will complain - but we don't allow ~ in the expr).
+// The term must be a valid expression (it could be a type incl. a
+// tilde term) but the type-checker will complain.
 type (
         _[t] t
-        _[/* ERROR unexpected ~ */ ~t] t
         _[t|t] t
-        _[/* ERROR unexpected ~ */ ~t|t] t
-        _[t| /* ERROR unexpected ~ */ ~t] t
-        _[/* ERROR unexpected ~ */ ~t|~t] t
+
+        // These are invalid and the type-checker will complain.
+        _[~t] t
+        _[~t|t] t
+        _[t|~t] t
+        _[~t|~t] t
 )
 
 type (
diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go
index 27f2904..33d329f 100644
--- a/src/cmd/compile/internal/types2/expr.go
+++ b/src/cmd/compile/internal/types2/expr.go
@@ -208,6 +208,12 @@
 		x.typ = ch.elem
 		check.hasCallOrRecv = true
 		return
+
+	case syntax.Tilde:
+		// Provide a better error position and message than what check.op below could do.
+		check.error(e, "cannot use ~ outside of interface or type constraint")
+		x.mode = invalid
+		return
 	}
 
 	if !check.op(unaryOpPredicates, x, e.Op) {
diff --git a/src/cmd/compile/internal/types2/testdata/check/expr0.go b/src/cmd/compile/internal/types2/testdata/check/expr0.go
index 1aac726..821b07f 100644
--- a/src/cmd/compile/internal/types2/testdata/check/expr0.go
+++ b/src/cmd/compile/internal/types2/testdata/check/expr0.go
@@ -178,3 +178,10 @@
 	_ = -g /* ERROR 2-valued g */ ()
 	_ = <-g /* ERROR 2-valued g */ ()
 }
+
+// ~ is accepted as unary operator only permitted in interface type elements
+var (
+	_ = ~ /* ERROR cannot use ~ outside of interface or type constraint */ 0
+	_ = ~ /* ERROR cannot use ~ outside of interface or type constraint */ "foo"
+	_ = ~ /* ERROR cannot use ~ outside of interface or type constraint */ i0
+)
\ No newline at end of file
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49482.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49482.go
index f289d2e..503d994 100644
--- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49482.go
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49482.go
@@ -22,4 +22,4 @@
 type _[P *struct /* ERROR "not an expression" */ {}| int /* ERROR "not an expression" */ ] struct{}
 
 // The following fails to parse, due to the '~'
-type _[P *struct /* ERROR "not an expression" */ {}|~ /* ERROR "unexpected ~" */ int] struct{}
+type _[P *struct /* ERROR "not an expression" */ {}|~int /* ERROR "not an expression" */ ] struct{}
diff --git a/test/fixedbugs/issue23587.go b/test/fixedbugs/issue23587.go
index 2308992..9040767 100644
--- a/test/fixedbugs/issue23587.go
+++ b/test/fixedbugs/issue23587.go
@@ -7,7 +7,7 @@
 package p
 
 func _(x int) {
-	_ = ~x    // ERROR "unexpected ~"
+	_ = ~x    // unary ~ permitted but the type-checker will complain
 }
 
 func _(x int) {