Repository: vyperlang/vyper

Issue:
BoolOps cannot accept >2 arguments
### Version Information

* vyper Version: 0.2.0b4

### What's your issue about?
```python
# Fails: Expected two arguments for a bool op
def foo():
    a: bool = True and True and False

# Fails: Expected two arguments for a bool op
def foo():
    a: bool = True or True or False

# This passes for some reason lol
def foo():
    a: bool = True or True and False

# This also passes lol
def foo():
    a: bool = True and (True and False)
```


### How can it be fixed?
Handle chained BoolOps similar to how regular Ops work (addition etc)

#### Cute Animal Picture
![insane rhino](https://images.theconversation.com/files/227656/original/file-20180714-27015-1j06axb.jpg)


Discussion:
@fubuloubu does this perhaps have to do with tri-ops notation like `1 < a < 10` which we don't support currently?
No, just straight booleans.
```python
def(a: bool, b: bool, c: bool) -> bool:
    return a and b and c
```
@fubuloubu @jacqueswww I'm taking a crack at this one.

Resolving patch:
diff --git a/vyper/parser/expr.py b/vyper/parser/expr.py
--- a/vyper/parser/expr.py
+++ b/vyper/parser/expr.py
@@ -586,22 +586,47 @@ def load_bytearray(side):
             raise TypeMismatchException("Unsupported types for comparison: %r %r" % (left_type, right_type), self.expr)
 
     def boolean_operations(self):
-        if len(self.expr.values) != 2:
-            raise StructureException("Expected two arguments for a bool op", self.expr)
-        if self.context.in_assignment and (isinstance(self.expr.values[0], ast.Call) or isinstance(self.expr.values[1], ast.Call)):
-            raise StructureException("Boolean operations with calls may not be performed on assignment", self.expr)
-
-        left = Expr.parse_value_expr(self.expr.values[0], self.context)
-        right = Expr.parse_value_expr(self.expr.values[1], self.context)
-        if not is_base_type(left.typ, 'bool') or not is_base_type(right.typ, 'bool'):
-            raise TypeMismatchException("Boolean operations can only be between booleans!", self.expr)
+        # Iterate through values
+        for value in self.expr.values:
+            # Check for calls at assignment
+            if self.context.in_assignment and isinstance(value, ast.Call):
+                raise StructureException("Boolean operations with calls may not be performed on assignment", self.expr)
+
+            # Check for boolean operations with non-boolean inputs
+            _expr = Expr.parse_value_expr(value, self.context)
+            if not is_base_type(_expr.typ, 'bool'):
+                raise TypeMismatchException("Boolean operations can only be between booleans!", self.expr)
+
+            # TODO: Handle special case of literals and simplify at compile time
+
+        # Check for valid ops
         if isinstance(self.expr.op, ast.And):
             op = 'and'
         elif isinstance(self.expr.op, ast.Or):
             op = 'or'
         else:
             raise Exception("Unsupported bool op: " + self.expr.op)
-        return LLLnode.from_list([op, left, right], typ='bool', pos=getpos(self.expr))
+
+        # Handle different numbers of inputs
+        count = len(self.expr.values)
+        if count < 2:
+            raise StructureException("Expected at least two arguments for a bool op", self.expr)
+        elif count == 2:
+            left = Expr.parse_value_expr(self.expr.values[0], self.context)
+            right = Expr.parse_value_expr(self.expr.values[1], self.context)
+            return LLLnode.from_list([op, left, right], typ='bool', pos=getpos(self.expr))
+        else:
+            left = Expr.parse_value_expr(self.expr.values[0], self.context)
+            right = Expr.parse_value_expr(self.expr.values[1], self.context)
+
+            p = ['seq', [op, left, right]]
+            values = self.expr.values[2:]
+            while len(values) > 0:
+                value = Expr.parse_value_expr(values[0], self.context)
+                p = [op, value, p]
+                values = values[1:]
+
+            return LLLnode.from_list(p, typ='bool', pos=getpos(self.expr))
 
     # Unary operations (only "not" supported)
     def unary_operations(self):


Test patch:
diff --git a/tests/parser/syntax/test_bool_ops.py b/tests/parser/syntax/test_bool_ops.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/syntax/test_bool_ops.py
@@ -0,0 +1,41 @@
+
+def test_convert_from_bool(get_contract_with_gas_estimation):
+    code = """
+@public
+def foo() -> bool:
+    val: bool = True and True and False
+    return val
+
+@public
+def bar() -> bool:
+    val: bool = True or True or False
+    return val
+
+@public
+def foobar() -> bool:
+    val: bool = False and True or False
+    return val
+
+@public
+def oof() -> bool:
+    val: bool = False or False or False or False or False or True
+    return val
+
+@public
+def rab() -> bool:
+    val: bool = True and True and True and True and True and False
+    return val
+
+@public
+def oofrab() -> bool:
+    val: bool = False and True or False and True or False and False or True
+    return val
+    """
+
+    c = get_contract_with_gas_estimation(code)
+    assert c.foo() is False
+    assert c.bar() is True
+    assert c.foobar() is False
+    assert c.oof() is True
+    assert c.rab() is False
+    assert c.oofrab() is True