Repository: vyperlang/vyper

Issue:
Incorrect `TypeMismatch` for `_MIN_MAX` with certain signed types
### Version Information

* vyper Version (output of `vyper --version`): 0.3.8+commit.d76c6ed2
* OS: OSX
* Python Version (output of `python --version`): 3.8.0

### What's your issue about?

When `min` or `max` is given two values `a,b` of type `intX` where `X>128`, if `a` or `b` is negative and the other one is greater than or equal to `2 ** 127`, the compiler outputs a `TypeMismatch` although they have the same type. This is due to the following check:

https://github.com/vyperlang/vyper/blob/187ab0eec8efbe19ed5046e4e947249e9d43141c/vyper/builtins/functions.py#L2018-L2019  

For example, compiling the following contract outputs the given error:

```Vyper
@external
def test():
    a:int256 = min(min_value(int256),max_value(int256))
```

```Bash
Error compiling: tests/customs/code.vy
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.8/bin/vyper", line 8, in <module>
    sys.exit(_parse_cli_args())
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 57, in _parse_cli_args
    return _parse_args(sys.argv[1:])
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 154, in _parse_args
    compiled = compile_files(
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 294, in compile_files
    compiler_data = vyper.compile_codes(
  File "/Users/trocher/Documents/thesis/vyper/vyper/evm/opcodes.py", line 226, in _wrapper
    return fn(*args, **kwargs)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 141, in compile_codes
    exc_handler(contract_name, exc)
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 189, in exc_handler
    raise exception
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 138, in compile_codes
    out[contract_name][output_format] = OUTPUT_FORMATS[output_format](compiler_data)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/output.py", line 82, in build_ir_output
    return compiler_data.ir_nodes
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 126, in ir_nodes
    ir, ir_runtime, sigs = self._ir_output
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 122, in _ir_output
    return generate_ir_nodes(self.global_ctx, self.no_optimize)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 117, in global_ctx
    return GlobalContext(self.vyper_module_folded)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 107, in vyper_module_folded
    module, storage_layout = self._folded_module
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 101, in _folded_module
    return generate_folded_ast(
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 228, in generate_folded_ast
    vy_ast.folding.fold(vyper_module_folded)
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/folding.py", line 43, in fold
    changed_nodes += replace_builtin_functions(vyper_module)
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/folding.py", line 131, in replace_builtin_functions
    new_node = func.evaluate(node)  # type: ignore
  File "/Users/trocher/Documents/thesis/vyper/vyper/builtins/functions.py", line 2019, in evaluate
    raise TypeMismatch("Cannot perform action between dislike numeric types", node)
vyper.exceptions.TypeMismatch: Cannot perform action between dislike numeric types
  contract "tests/customs/code.vy:3", function "test", line 3:15 
       2 def test():
  ---> 3     a:int256 = min(min_value(int256),max_value(int256))
  ----------------------^
       4
```


Discussion:

Resolving patch:
diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py
--- a/vyper/builtins/functions.py
+++ b/vyper/builtins/functions.py
@@ -2016,7 +2016,11 @@ def evaluate(self, node):
             or max(left, right) > SizeLimits.MAX_AST_DECIMAL
         ):
             raise InvalidType("Decimal value is outside of allowable range", node)
-        if isinstance(left, int) and (min(left, right) < 0 and max(left, right) >= 2 ** 127):
+
+        types_list = get_common_types(
+            *node.args, filter_fn=lambda x: isinstance(x, (IntegerT, DecimalT))
+        )
+        if not types_list:
             raise TypeMismatch("Cannot perform action between dislike numeric types", node)
 
         value = self._eval_fn(left, right)
@@ -2575,10 +2579,14 @@ def evaluate(self, node):
         val = self._eval(input_type)
 
         if isinstance(input_type, DecimalT):
-            return vy_ast.Decimal.from_node(node, value=val)
+            ret = vy_ast.Decimal.from_node(node, value=val)
 
         if isinstance(input_type, IntegerT):
-            return vy_ast.Int.from_node(node, value=val)
+            ret = vy_ast.Int.from_node(node, value=val)
+
+        # TODO: to change to known_type once #3213 is merged
+        ret._metadata["type"] = input_type
+        return ret
 
 
 class MinValue(_MinMaxValue):
diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py
--- a/vyper/semantics/analysis/local.py
+++ b/vyper/semantics/analysis/local.py
@@ -381,6 +381,9 @@ def visit_For(self, node):
                     if args[0].value >= args[1].value:
                         raise StructureException("Second value must be > first value", args[1])
 
+                if not type_list:
+                    raise TypeMismatch("Iterator values are of different types", node.iter)
+
         else:
             # iteration over a variable or literal list
             if isinstance(node.iter, vy_ast.List) and len(node.iter.elements) == 0:


Test patch:
diff --git a/tests/parser/functions/test_minmax.py b/tests/parser/functions/test_minmax.py
--- a/tests/parser/functions/test_minmax.py
+++ b/tests/parser/functions/test_minmax.py
@@ -24,7 +24,7 @@ def goo() -> uint256:
 
 
 @pytest.mark.parametrize("return_type", sorted(IntegerT.all()))
-def test_minmax_var_and_literal(get_contract_with_gas_estimation, return_type):
+def test_minmax_var_and_literal_and_bultin(get_contract_with_gas_estimation, return_type):
     """
     Tests to verify that min and max work as expected when a variable/literal
     and a literal are passed for all integer types.
@@ -51,12 +51,22 @@ def both_literals_max() -> {return_type}:
 @external
 def both_literals_min() -> {return_type}:
     return min({lo}, 2)
+
+@external
+def both_builtins_max() -> {return_type}:
+    return max(min_value({return_type}), max_value({return_type}))
+
+@external
+def both_builtins_min() -> {return_type}:
+    return min(min_value({return_type}), max_value({return_type}))
 """
     c = get_contract_with_gas_estimation(code)
     assert c.foo() == hi
     assert c.bar() == lo
     assert c.both_literals_max() == hi
     assert c.both_literals_min() == lo
+    assert c.both_builtins_max() == hi
+    assert c.both_builtins_min() == lo
 
 
 def test_max_var_uint256_literal_int128(get_contract_with_gas_estimation):
diff --git a/tests/parser/syntax/test_minmax.py b/tests/parser/syntax/test_minmax.py
--- a/tests/parser/syntax/test_minmax.py
+++ b/tests/parser/syntax/test_minmax.py
@@ -1,17 +1,27 @@
 import pytest
 
-from vyper.exceptions import InvalidType
+from vyper.exceptions import InvalidType, TypeMismatch
 
 fail_list = [
-    """
+    (
+        """
 @external
 def foo():
     y: int128 = min(7, 0x1234567890123456789012345678901234567890)
-    """
+    """,
+        InvalidType,
+    ),
+    (
+        """
+@external
+def foo():
+    a: int16 = min(min_value(int16), max_value(int8))
+    """,
+        TypeMismatch,
+    ),
 ]
 
 
-@pytest.mark.parametrize("bad_code", fail_list)
-def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code):
-
-    assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), InvalidType)
+@pytest.mark.parametrize("bad_code,exc", fail_list)
+def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code, exc):
+    assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code), exc)