Add IntRange.TryFoo methods
diff --git a/lang/check/bounds.go b/lang/check/bounds.go
index 7275ea9..266093d 100644
--- a/lang/check/bounds.go
+++ b/lang/check/bounds.go
@@ -1384,7 +1384,7 @@
 			return bounds{}, fmt.Errorf("check: divide/modulus op argument %q is possibly non-positive", rhs.Str(q.tm))
 		}
 		if op == t.IDXBinarySlash {
-			nb, _ := lb.Quo(rb)
+			nb, _ := lb.TryQuo(rb)
 			return nb, nil
 		}
 		return bounds{
@@ -1413,14 +1413,14 @@
 
 		switch op {
 		case t.IDXBinaryShiftL:
-			nb, _ := lb.Lsh(rb)
+			nb, _ := lb.TryLsh(rb)
 			return nb, nil
 		case t.IDXBinaryTildeModShiftL:
-			nb, _ := lb.Lsh(rb)
+			nb, _ := lb.TryLsh(rb)
 			nb[1] = min(nb[1], typeBounds[1])
 			return nb, nil
 		case t.IDXBinaryShiftR:
-			nb, _ := lb.Rsh(rb)
+			nb, _ := lb.TryRsh(rb)
 			return nb, nil
 		}
 
@@ -1435,11 +1435,9 @@
 		}
 		switch op {
 		case t.IDXBinaryAmp:
-			nb, _ := lb.And(rb)
-			return nb, nil
+			return lb.And(rb), nil
 		case t.IDXBinaryPipe:
-			nb, _ := lb.Or(rb)
-			return nb, nil
+			return lb.Or(rb), nil
 		case t.IDXBinaryHat:
 			z := max(lb[1], rb[1])
 			// Return [0, z rounded up to the next power-of-2-minus-1]. This is
diff --git a/lib/interval/interval.go b/lib/interval/interval.go
index a8d5f32..b1d2ea4 100644
--- a/lib/interval/interval.go
+++ b/lib/interval/interval.go
@@ -189,6 +189,13 @@
 // *big.Int pointer values. Specifically, after "z = x.Add(y)", mutating
 // "*z[0]" will not affect "*x[0]", "*x[1]", "*y[0]" or "*y[1]".
 //
+// Those operator-like methods come in two forms: Foo and TryFoo. The TryFoo
+// forms return (z IntRange, ok bool). The bool indicates success, as
+// operations like dividing by zero or shifting by a negative value can fail.
+// When TryFoo can never fail, there is also a Foo method that omits the
+// always-true bool. For example, there are Add, TryAdd and TryLsh methods, but
+// no Lsh method.
+//
 // A subtle point is that an interval's minimum or maximum can be infinite, but
 // if an integer value i is known to be within such an interval, i's possible
 // values are arbitrarily large but not infinite. Specifically, 0*i is
@@ -413,6 +420,11 @@
 	return z
 }
 
+// TryUnite returns (x.Unite(y), true).
+func (x IntRange) TryUnite(y IntRange) (z IntRange, ok bool) {
+	return x.Unite(y), true
+}
+
 func (x *IntRange) inPlaceUnite(y IntRange) {
 	if y.Empty() {
 		return
@@ -478,6 +490,11 @@
 	return z
 }
 
+// TryIntersect returns (x.Intersect(y), true).
+func (x IntRange) TryIntersect(y IntRange) (z IntRange, ok bool) {
+	return x.Intersect(y), true
+}
+
 // Add returns z = x + y.
 func (x IntRange) Add(y IntRange) (z IntRange) {
 	if x.Empty() || y.Empty() {
@@ -492,6 +509,11 @@
 	return z
 }
 
+// TryAdd returns (x.Add(y), true).
+func (x IntRange) TryAdd(y IntRange) (z IntRange, ok bool) {
+	return x.Add(y), true
+}
+
 // Sub returns z = x - y.
 func (x IntRange) Sub(y IntRange) (z IntRange) {
 	if x.Empty() || y.Empty() {
@@ -506,17 +528,27 @@
 	return z
 }
 
+// TrySub returns (x.Sub(y), true).
+func (x IntRange) TrySub(y IntRange) (z IntRange, ok bool) {
+	return x.Sub(y), true
+}
+
 // Mul returns z = x * y.
 func (x IntRange) Mul(y IntRange) (z IntRange) {
 	return x.mulLsh(y, false)
 }
 
-// Lsh returns z = x << y.
+// TryMul returns (x.Mul(y), true).
+func (x IntRange) TryMul(y IntRange) (z IntRange, ok bool) {
+	return x.Mul(y), true
+}
+
+// TryLsh returns z = x << y.
 //
 // ok is false (and z will be IntRange{nil, nil}) if x is non-empty and y
 // contains at least one negative value, as it's invalid to shift by a negative
 // number. Otherwise, ok is true.
-func (x IntRange) Lsh(y IntRange) (z IntRange, ok bool) {
+func (x IntRange) TryLsh(y IntRange) (z IntRange, ok bool) {
 	if !x.Empty() && y.ContainsNegative() {
 		return IntRange{}, false
 	}
@@ -602,12 +634,12 @@
 	return ret.toIntRange()
 }
 
-// Quo returns z = x / y. Like the big.Int.Quo method (and unlike the
+// TryQuo returns z = x / y. Like the big.Int.Quo method (and unlike the
 // big.Int.Div method), it truncates towards zero.
 //
 // ok is false (and z will be IntRange{nil, nil}) if x is non-empty and y
 // contains zero, as it's invalid to divide by zero. Otherwise, ok is true.
-func (x IntRange) Quo(y IntRange) (z IntRange, ok bool) {
+func (x IntRange) TryQuo(y IntRange) (z IntRange, ok bool) {
 	if x.Empty() || y.Empty() {
 		return makeEmptyRange(), true
 	}
@@ -692,12 +724,12 @@
 	return ret.toIntRange(), true
 }
 
-// Rsh returns z = x >> y.
+// TryRsh returns z = x >> y.
 //
 // ok is false (and z will be IntRange{nil, nil}) if x is non-empty and y
 // contains at least one negative value, as it's invalid to shift by a negative
 // number. Otherwise, ok is true.
-func (x IntRange) Rsh(y IntRange) (z IntRange, ok bool) {
+func (x IntRange) TryRsh(y IntRange) (z IntRange, ok bool) {
 	if x.Empty() || y.Empty() {
 		return makeEmptyRange(), true
 	}
@@ -750,15 +782,12 @@
 }
 
 // And returns z = x & y.
-//
-// ok is false (and z will be IntRange{nil, nil}) if x or y contains at least
-// one negative value. Otherwise, ok is true.
-func (x IntRange) And(y IntRange) (z IntRange, ok bool) {
+func (x IntRange) And(y IntRange) (z IntRange) {
 	if x.Empty() || y.Empty() {
-		return makeEmptyRange(), true
+		return makeEmptyRange()
 	}
 	if !x.ContainsNegative() && !y.ContainsNegative() {
-		return andBothNonNeg(x, y), true
+		return andBothNonNeg(x, y)
 	}
 
 	negX, nonX, hasNegX, hasNonX := x.split2Ways()
@@ -791,7 +820,12 @@
 			z.inPlaceUnite(andBothNonNeg(nonX, nonY))
 		}
 	}
-	return z, true
+	return z
+}
+
+// TryAnd returns (x.And(y), true).
+func (x IntRange) TryAnd(y IntRange) (z IntRange, ok bool) {
+	return x.And(y), true
 }
 
 func andBothNonNeg(x IntRange, y IntRange) (z IntRange) {
@@ -855,15 +889,12 @@
 }
 
 // Or returns z = x | y.
-//
-// ok is false (and z will be IntRange{nil, nil}) if x or y contains at least
-// one negative value. Otherwise, ok is true.
-func (x IntRange) Or(y IntRange) (z IntRange, ok bool) {
+func (x IntRange) Or(y IntRange) (z IntRange) {
 	if x.Empty() || y.Empty() {
-		return makeEmptyRange(), true
+		return makeEmptyRange()
 	}
 	if !x.ContainsNegative() && !y.ContainsNegative() {
-		return orBothNonNeg(x, y), true
+		return orBothNonNeg(x, y)
 	}
 
 	negX, nonX, hasNegX, hasNonX := x.split2Ways()
@@ -896,7 +927,12 @@
 			z.inPlaceUnite(orBothNonNeg(nonX, nonY))
 		}
 	}
-	return z, true
+	return z
+}
+
+// TryOr returns (x.Or(y), true).
+func (x IntRange) TryOr(y IntRange) (z IntRange, ok bool) {
+	return x.Or(y), true
 }
 
 func orBothNonNeg(x IntRange, y IntRange) (z IntRange) {
diff --git a/lib/interval/interval_test.go b/lib/interval/interval_test.go
index b3cd1ec..1727195 100644
--- a/lib/interval/interval_test.go
+++ b/lib/interval/interval_test.go
@@ -441,16 +441,16 @@
 }
 
 var intOperators = map[rune]func(IntRange, IntRange) (IntRange, bool){
-	'∪': func(x IntRange, y IntRange) (z IntRange, ok bool) { return x.Unite(y), true },
-	'∩': func(x IntRange, y IntRange) (z IntRange, ok bool) { return x.Intersect(y), true },
-	'+': func(x IntRange, y IntRange) (z IntRange, ok bool) { return x.Add(y), true },
-	'-': func(x IntRange, y IntRange) (z IntRange, ok bool) { return x.Sub(y), true },
-	'*': func(x IntRange, y IntRange) (z IntRange, ok bool) { return x.Mul(y), true },
-	'/': IntRange.Quo,
-	'«': IntRange.Lsh,
-	'»': IntRange.Rsh,
-	'&': IntRange.And,
-	'|': IntRange.Or,
+	'∪': IntRange.TryUnite,
+	'∩': IntRange.TryIntersect,
+	'+': IntRange.TryAdd,
+	'-': IntRange.TrySub,
+	'*': IntRange.TryMul,
+	'/': IntRange.TryQuo,
+	'«': IntRange.TryLsh,
+	'»': IntRange.TryRsh,
+	'&': IntRange.TryAnd,
+	'|': IntRange.TryOr,
 }
 
 var intOperatorsKeys []rune
@@ -913,10 +913,10 @@
 			x := IntRange{x0, x1}
 			want := x
 
-			if got, _ := x.And(minusOne); !got.Eq(want) {
+			if got := x.And(minusOne); !got.Eq(want) {
 				tt.Fatalf("%v & -1: got %v, want %v", x, got, want)
 			}
-			if got, _ := minusOne.And(x); !got.Eq(want) {
+			if got := minusOne.And(x); !got.Eq(want) {
 				tt.Fatalf("-1 & %v: got %v, want %v", x, got, want)
 			}
 		}
@@ -933,10 +933,10 @@
 				want = sharedEmptyRange
 			}
 
-			if got, _ := x.And(zero); !got.Eq(want) {
+			if got := x.And(zero); !got.Eq(want) {
 				tt.Fatalf("%v & +0: got %v, want %v", x, got, want)
 			}
-			if got, _ := zero.And(x); !got.Eq(want) {
+			if got := zero.And(x); !got.Eq(want) {
 				tt.Fatalf("+0 & %v: got %v, want %v", x, got, want)
 			}
 		}
@@ -953,10 +953,10 @@
 				want = sharedEmptyRange
 			}
 
-			if got, _ := x.Or(minusOne); !got.Eq(want) {
+			if got := x.Or(minusOne); !got.Eq(want) {
 				tt.Fatalf("%v | -1: got %v, want %v", x, got, want)
 			}
-			if got, _ := minusOne.Or(x); !got.Eq(want) {
+			if got := minusOne.Or(x); !got.Eq(want) {
 				tt.Fatalf("-1 | %v: got %v, want %v", x, got, want)
 			}
 		}
@@ -970,10 +970,10 @@
 			x := IntRange{x0, x1}
 			want := x
 
-			if got, _ := x.Or(zero); !got.Eq(want) {
+			if got := x.Or(zero); !got.Eq(want) {
 				tt.Fatalf("%v | +0: got %v, want %v", x, got, want)
 			}
-			if got, _ := zero.Or(x); !got.Eq(want) {
+			if got := zero.Or(x); !got.Eq(want) {
 				tt.Fatalf("+0 | %v: got %v, want %v", x, got, want)
 			}
 		}