tools/fix: eliminate x & _ and unnecessary parens

Change-Id: I4612a0578703d7b13328d052ad6bae85c46cf69c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8302
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/tools/fix/fix.go b/tools/fix/fix.go
index b464270..1e814fb 100644
--- a/tools/fix/fix.go
+++ b/tools/fix/fix.go
@@ -31,7 +31,8 @@
 type Option func(*options)
 
 type options struct {
-	simplify bool
+	simplify   bool
+	deprecated bool
 }
 
 // Simplify enables fixes that simplify the code, but are not strictly
@@ -265,38 +266,9 @@
 	// 	return true
 	// }, nil).(*ast.File)
 
-	if !options.simplify {
-		return f
+	if options.simplify {
+		f = simplify(f)
 	}
 
-	// Rewrite disjunctions with _ to _.
-	f = astutil.Apply(f, func(c astutil.Cursor) bool {
-		if x := findTop(c.Node()); x != nil {
-			c.Replace(x)
-		}
-		return true
-	}, nil).(*ast.File)
-
 	return f
 }
-
-func findTop(x ast.Node) ast.Expr {
-	switch x := x.(type) {
-	case *ast.BinaryExpr:
-		if x.Op != token.OR {
-			break
-		}
-		if v := findTop(x.X); v != nil {
-			return v
-		}
-		if v := findTop(x.Y); v != nil {
-			return v
-		}
-
-	case *ast.Ident:
-		if x.Name == "_" {
-			return x
-		}
-	}
-	return nil
-}
diff --git a/tools/fix/fix_test.go b/tools/fix/fix_test.go
index 63f7f99..3f251ac 100644
--- a/tools/fix/fix_test.go
+++ b/tools/fix/fix_test.go
@@ -89,9 +89,19 @@
 	}, {
 		simplify: true,
 		in: `
-		y: _ | {[string]: int}
+		x1: 3 & _
+		x2: _ | {[string]: int}
+		x3: 4 & (9 | _)
+		x4: (_ | 9) & 4
+		x5: (_ & 9) & 4
+		x6: 4 & (_ & 9)
 		`,
-		out: `y: _
+		out: `x1: 3
+x2: _
+x3: 4
+x4: 4
+x5: 9 & 4
+x6: 4 & 9
 `,
 
 		// 	}, {
diff --git a/tools/fix/simplify.go b/tools/fix/simplify.go
new file mode 100644
index 0000000..623d5c3
--- /dev/null
+++ b/tools/fix/simplify.go
@@ -0,0 +1,73 @@
+// Copyright 2021 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fix
+
+import (
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/ast/astutil"
+	"cuelang.org/go/cue/token"
+)
+
+func simplify(f *ast.File) *ast.File {
+	// Rewrite disjunctions with _ to _.
+	f = astutil.Apply(f, nil, func(c astutil.Cursor) bool {
+		if x, ok := c.Node().(ast.Expr); ok {
+			if y := elideTop(x); x != y {
+				c.Replace(y)
+			}
+		}
+		return true
+	}).(*ast.File)
+
+	return f
+}
+
+func elideTop(x ast.Expr) ast.Expr {
+	switch x := x.(type) {
+	case *ast.BinaryExpr:
+		switch x.Op {
+		case token.OR:
+			if isTop(x.X) {
+				return x.X
+			}
+			if isTop(x.Y) {
+				ast.SetRelPos(x.Y, token.NoRelPos)
+				return x.Y
+			}
+
+		case token.AND:
+			if isTop(x.X) {
+				ast.SetRelPos(x.Y, token.NoRelPos)
+				return x.Y
+			}
+			if isTop(x.Y) {
+				return x.X
+			}
+		}
+
+	case *ast.ParenExpr:
+		switch x.X.(type) {
+		case *ast.BinaryExpr, *ast.UnaryExpr:
+		default:
+			return x.X
+		}
+	}
+	return x
+}
+
+func isTop(x ast.Expr) bool {
+	v, ok := x.(*ast.Ident)
+	return ok && v.Name == "_"
+}