Repository: python/mypy

Issue:
Float multiplication of numpy.ndarray in lambda is incorrectly analysed
Note: if you are reporting a wrong signature of a function or a class in
the standard library, then the typeshed tracker is better suited
for this report: https://github.com/python/typeshed/issues

Please provide more information to help us understand the issue:

* Are you reporting a bug, or opening a feature request?

Bug.

* Please insert below the code you are checking with mypy,
  or a mock-up repro if the source is private. We would appreciate
  if you try to simplify your case to a minimal repro.

```python
from typing import NewType, Tuple, Deque

import numpy as np


class Point(np.ndarray):
    def __new__(cls, x: float, y: float) -> np.ndarray:
        return np.array((x, y), dtype=np.float32)


Nose = NewType("Nose", Tuple[Point, ...])


def average_position(positions: Deque) -> Nose:
    factor = 1.0 / len(positions)
    point_sum = [Point(0.0, 0.0) for _ in range(10)]
    for nose in positions:
        for i, point in enumerate(nose):
            point_sum[i] += point
    avg_pos = Nose(tuple(map(lambda p: factor * p, point_sum)))
    return avg_pos


def average_position_with_multiply(positions: Deque) -> Nose:
    factor = 1.0 / len(positions)
    point_sum = [Point(0.0, 0.0) for _ in range(10)]
    for nose in positions:
        for i, point in enumerate(nose):
            point_sum[i] += point
    avg_pos = Nose(tuple(map(lambda p: np.multiply(factor, p), point_sum)))
    return avg_pos

```

* What is the actual behavior/output?

Mypy gives the errors below:
```python
bug.py:20: error: Argument 1 to "map" has incompatible type "Callable[[Point], float]"; expected "Callable[[Point], Point]"
bug.py:20: error: Incompatible return value type (got "float", expected "Point")
Found 2 errors in 1 file (checked 1 source file)

```

In the lambda in `average_position()`, mypy incorrectly (?) determines the type as `Callable[[Point], float]`, while using the NumPy function `np.multiply()` (which does the same thing) gives no error. Both functions output the same correct value.

* What is the behavior/output you expect?
There should be no error.

* What are the versions of mypy and Python you are using?
  Do you see the same issue after installing mypy from Git master?

```plain
mypy==0.740
Python 3.8.0
```

* What are the mypy flags you are using? (For example --strict-optional)

```ini
[mypy]
python_version = 3.8

[mypy-cv2,dlib,numpy]
ignore_missing_imports = True
```

I am unsure if this is actually an error, or if I've done something wrong somewhere, but both functions run and output correctly.



Discussion:
The root of the problem is that mypy incorrectly infers the type of multiplying `Point` by a float. Here's a shorter example:

```py
from typing import Any

C: Any
class D(C):
    pass

reveal_type(0.5 * D())   # float, but should be Any
```

As a workaround, you can try installing stubs for numpy (https://github.com/numpy/numpy-stubs).

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -2470,6 +2470,14 @@ def lookup_definer(typ: Instance, attr_name: str) -> Optional[str]:
             else:
                 return result
 
+        # We finish invoking above operators and no early return happens. Therefore,
+        # we check if either the LHS or the RHS is Instance and fallbacks to Any,
+        # if so, we also return Any
+        if ((isinstance(left_type, Instance) and left_type.type.fallback_to_any) or
+                (isinstance(right_type, Instance) and right_type.type.fallback_to_any)):
+            any_type = AnyType(TypeOfAny.special_form)
+            return any_type, any_type
+
         # STEP 4b:
         # Sometimes, the variants list is empty. In that case, we fall-back to attempting to
         # call the __op__ method (even though it's missing).


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -6575,3 +6575,24 @@ class C:
 from typing import Callable
 class C:
     x: Callable[[C], int] = lambda x: x.y.g()  # E: "C" has no attribute "y"
+
+[case testOpWithInheritedFromAny]
+from typing import Any
+C: Any
+class D(C):
+    pass
+
+class D1(C):
+    def __add__(self, rhs: float) -> D1:
+        return self
+
+reveal_type(0.5 + C)  # N: Revealed type is 'Any'
+
+reveal_type(0.5 + D())  # N: Revealed type is 'Any'
+reveal_type(D() + 0.5)  # N: Revealed type is 'Any'
+reveal_type("str" + D())  # N: Revealed type is 'builtins.str'
+reveal_type(D() + "str")  # N: Revealed type is 'Any'
+
+
+reveal_type(0.5 + D1())  # N: Revealed type is 'Any'
+reveal_type(D1() + 0.5)  # N: Revealed type is '__main__.D1'