Repository: python/mypy

Issue:
mypy 1.0.0 does not include error code for `"await" outside function` so it cannot be ignored when linting notebooks
**Bug Report**

In mypy 0.991, mypy would assign error code "misc" when `await` was used outside a function. Though "misc" is non-specific, having an error code allowed for ignoring these errors when linting Jupyter notebook files (using `nbqa mypy file.ipynb` with [nbqa](https://github.com/nbQA-dev/nbQA)). Jupyter notebooks already automatically run cells in an async awaited eventloop context (see [here](https://ipython.readthedocs.io/en/stable/interactive/autoawait.html#using-autoawait-in-a-notebook-ipykernel)), so it's important to be able to ignore these top-level `await` usages in ipynb files. For instance, Pylance added logic to handle this situation directly here https://github.com/microsoft/pylance-release/issues/1754#issuecomment-1028462082.

As of mypy 1.0.0, mypy no longer assigns an error code when `await` is used outside a function, and treats it as a blocking error that prevents further checking. This means it's now no longer possible to lint notebook files that use `await` with mypy+nbqa.

I'm guessing this change in behavior was introduced in https://github.com/python/mypy/pull/14486 [here](https://github.com/python/mypy/pull/14486/files#diff-f782aad4853edceb67a88efe43d2869579d075a77b8e8dd2add91e818a9fa164R5168)

**To Reproduce**

Run `await` in the top level of some file (notebook or otherwise):

```python
async def foo():
    pass

await foo()
```

Compare:
- mypy 1.0.0: https://mypy-play.net/?mypy=1.0.0&python=3.11&gist=a97bbbf61fb0e0747b4f0a74d4a2124c
    ```
    main.py:4: error: "await" outside function
    Found 1 error in 1 file (errors prevented further checking)
    ```
- mypy 0.991: https://mypy-play.net/?mypy=0.991&python=3.11&gist=a97bbbf61fb0e0747b4f0a74d4a2124c
    ```
    main.py:4: error: "await" outside function  [misc]
    Found 1 error in 1 file (checked 1 source file)
    ```


**Expected Behavior**

mypy does *not* halt further checking, and mypy uses an error-code for `"await" outside function`, so that users can choose to disable the warning when that occurs. That will allow mypy to continue to be used/useful for Jupyter notebook files (for instance, setting `--disable-error-code=misc` when using `nbqa`).

**Actual Behavior**

mypy returns a blocking error and does not provide a workaround.

**Your Environment**

- Mypy version used: 1.0.1 or 1.0.0
- Mypy command-line flags: `--disable-error-code=misc`
- Mypy configuration options from `mypy.ini` (and other config files): any
- Python version used: 3.10+



Discussion:
cc @JukkaL reasonable use case for this to be a non-blocking error

Resolving patch:
diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py
--- a/mypy/errorcodes.py
+++ b/mypy/errorcodes.py
@@ -132,6 +132,9 @@ def __str__(self) -> str:
 SAFE_SUPER: Final = ErrorCode(
     "safe-super", "Warn about calls to abstract methods with empty/trivial bodies", "General"
 )
+TOP_LEVEL_AWAIT: Final = ErrorCode(
+    "top-level-await", "Warn about top level await experessions", "General"
+)
 
 # These error codes aren't enabled by default.
 NO_UNTYPED_DEF: Final[ErrorCode] = ErrorCode(
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -5249,7 +5249,9 @@ def visit_yield_expr(self, e: YieldExpr) -> None:
     def visit_await_expr(self, expr: AwaitExpr) -> None:
         if not self.is_func_scope() or not self.function_stack:
             # We check both because is_function_scope() returns True inside comprehensions.
-            self.fail('"await" outside function', expr, serious=True, blocker=True)
+            # This is not a blocker, because some enviroments (like ipython)
+            # support top level awaits.
+            self.fail('"await" outside function', expr, serious=True, code=codes.TOP_LEVEL_AWAIT)
         elif not self.function_stack[-1].is_coroutine:
             self.fail('"await" outside coroutine ("async def")', expr, serious=True, blocker=True)
         expr.expr.accept(self)


Test patch:
diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test
--- a/test-data/unit/check-async-await.test
+++ b/test-data/unit/check-async-await.test
@@ -945,11 +945,15 @@ async def bar(x: Union[A, B]) -> None:
 [typing fixtures/typing-async.pyi]
 
 [case testInvalidComprehensionNoCrash]
+# flags: --show-error-codes
 async def foo(x: int) -> int: ...
 
-crasher = [await foo(x) for x in [1, 2, 3]]  # E: "await" outside function
+# These are allowed in some cases:
+top_level = await foo(1)  # E: "await" outside function  [top-level-await]
+crasher = [await foo(x) for x in [1, 2, 3]]  # E: "await" outside function  [top-level-await]
 
 def bad() -> None:
+    # These are always critical / syntax issues:
     y = [await foo(x) for x in [1, 2, 3]]  # E: "await" outside coroutine ("async def")
 async def good() -> None:
     y = [await foo(x) for x in [1, 2, 3]]  # OK