Repository: python/mypy

Issue:
Async function reported as "Special form" in HTML output.
(I was asked to report the following as a bug.)

Take the following function:
```python
async def some_function() -> None:
    pass
```

It is reported as "Special form":
![image](https://user-images.githubusercontent.com/216638/57195630-2975cc80-6f55-11e9-9420-1e6cfc354c74.png)

This is something internal to mypy, and should not end up in the generated HTML.


Discussion:
Another similar issue:
Take this code:
```python
from typing import NamedTuple

def a() -> None:
    _FuzzyMatch(0, 0)

_FuzzyMatch = NamedTuple('_FuzzyMatch', [
    ('match_length', int),
    ('start_pos', int),
])

def b() -> None:
    _FuzzyMatch(0, 0)
```

![image](https://user-images.githubusercontent.com/216638/57200535-9c029e80-6f8d-11e9-8f38-fc15cf69bcae.png)

Somehow the forward reference becomes red...
looking

Resolving patch:
diff --git a/mypy/report.py b/mypy/report.py
--- a/mypy/report.py
+++ b/mypy/report.py
@@ -235,6 +235,7 @@ def _report_any_exprs(self) -> None:
             coverage = (float(num_total - num_any) / float(num_total)) * 100
             coverage_str = '{:.2f}%'.format(coverage)
             rows.append([filename, str(num_any), str(num_total), coverage_str])
+        rows.sort(key=lambda x: x[0])
         total_row = ["Total", str(total_any), str(total_expr), '{:.2f}%'.format(total_coverage)]
         self._write_out_report('any-exprs.txt', column_names, rows, total_row)
 
@@ -249,6 +250,7 @@ def _report_types_of_anys(self) -> None:
         rows = []  # type: List[List[str]]
         for filename, counter in self.any_types_counter.items():
             rows.append([filename] + [str(counter[typ]) for typ in type_of_any_name_map])
+        rows.sort(key=lambda x: x[0])
         total_row = [total_row_name] + [str(total_counter[typ])
                                         for typ in type_of_any_name_map]
         self._write_out_report('types-of-anys.txt', column_names, rows, total_row)
diff --git a/mypy/stats.py b/mypy/stats.py
--- a/mypy/stats.py
+++ b/mypy/stats.py
@@ -185,7 +185,7 @@ def type(self, t: Optional[Type]) -> None:
             self.record_line(self.line, TYPE_UNANALYZED)
             return
 
-        if isinstance(t, AnyType) and t.type_of_any == TypeOfAny.special_form:
+        if isinstance(t, AnyType) and is_special_form_any(t):
             # This is not a real Any type, so don't collect stats for it.
             return
 
@@ -204,10 +204,9 @@ def type(self, t: Optional[Type]) -> None:
 
         for typ in collect_all_inner_types(t) + [t]:
             if isinstance(typ, AnyType):
-                if typ.type_of_any == TypeOfAny.from_another_any:
-                    assert typ.source_any
-                    assert typ.source_any.type_of_any != TypeOfAny.from_another_any
-                    typ = typ.source_any
+                typ = get_original_any(typ)
+                if is_special_form_any(typ):
+                    continue
                 self.type_of_any_counter[typ.type_of_any] += 1
                 self.num_any_types += 1
                 if self.line in self.any_line_map:
@@ -276,7 +275,7 @@ def __init__(self) -> None:
         super().__init__(any)
 
     def visit_any(self, t: AnyType) -> bool:
-        return True
+        return not is_special_form_any(t)
 
     def visit_instance(self, t: Instance) -> bool:
         if t.type.fullname() == 'builtins.tuple':
@@ -308,3 +307,15 @@ def is_complex(t: Type) -> bool:
 def ensure_dir_exists(dir: str) -> None:
     if not os.path.exists(dir):
         os.makedirs(dir)
+
+
+def is_special_form_any(t: AnyType) -> bool:
+    return get_original_any(t).type_of_any == TypeOfAny.special_form
+
+
+def get_original_any(t: AnyType) -> AnyType:
+    if t.type_of_any == TypeOfAny.from_another_any:
+        assert t.source_any
+        assert t.source_any.type_of_any != TypeOfAny.from_another_any
+        t = t.source_any
+    return t


Test patch:
diff --git a/test-data/unit/reports.test b/test-data/unit/reports.test
--- a/test-data/unit/reports.test
+++ b/test-data/unit/reports.test
@@ -380,3 +380,45 @@ empty = False
 [outfile report/linecount.txt]
       1       1      0      0 total
       1       1      0      0 a
+
+[case testAnyExprReportIgnoresSpecialForms]
+# cmd: mypy --any-exprs-report report i.py j.py k.py l.py
+
+[file i.py]
+async def some_function() -> None:
+    pass
+
+[file j.py]
+from typing import Any
+
+async def some_function() -> Any:
+    pass
+
+[file k.py]
+from typing import NamedTuple
+
+def a() -> None:
+    _FuzzyMatch(0, 0)
+
+_FuzzyMatch = NamedTuple('_FuzzyMatch', [
+    ('match_length', int),
+    ('start_pos', int),
+])
+
+def b() -> None:
+    _FuzzyMatch(0, 0)
+
+[file l.py]
+async def some_function(x) -> None:
+    pass
+
+[file report/any-exprs.txt]
+[outfile report/types-of-anys.txt]
+ Name   Unannotated   Explicit   Unimported   Omitted Generics   Error   Special Form   Implementation Artifact
+-----------------------------------------------------------------------------------------------------------------
+    i             0          0            0                  0       0              0                         0
+    j             0          1            0                  0       0              0                         0
+    k             0          0            0                  0       0              0                         0
+    l             1          0            0                  0       0              0                         0
+-----------------------------------------------------------------------------------------------------------------
+Total             1          1            0                  0       0              0                         0