Repository: python/mypy

Issue:
`assert_type()` says an int is not an int
The following code:

```python
from typing_extensions import assert_type

assert_type(42, int)
```

fails to typecheck with the following message:

```
assert-type01.py:3: error: Expression is of type "int", not "int"
```

**Your Environment**

- Mypy version used: both 0.960 and commit 1636a0549670e1b75e2987c16ef26ebf91dfbde9
- Mypy command-line flags: none
- Mypy configuration options from `mypy.ini` (and other config files): none
- Python version used: 3.9.13
- Operating system and version: macOS 11.6.6


Discussion:
Lol, it's probably `Literal[42]`. So bad error message, but there should probably still be an error? Jelle will confirm.
Yeah, pyright also gives an error `error: "assert_type" mismatch: expected "int" but received "Literal[42]"`
Implementation hints: 42 has type `Instance` with the `last_known_value` attribute set to the literal type. If the string representations of types in the assert_type error are the same, we can perhaps try again while using `last_known_value` if it exists.
Looks like I self-assigned this but then forgot about it. I'm happy to leave this to a sprint contributor.

As the author of assert_type(), I think it's reasonable for a type checker to fail on `assert_type(42, int)` if it infers 42 to be of type `Literal[42]`. `assert_type()` is meant to check that the types are exactly the same.

mypy's current behavior is actually different from what this issue reports:

```
from typing_extensions import assert_type, Literal

assert_type(42, int)  # passes
assert_type(42, Literal[42]) # also passes
```

I think this behavior is wrong: exactly one of these two should succeed, but not both. Which of the two is up to the type checker to decide. For both pyanalyze and pyright, `42` is inferred as type `Literal[42]` and therefore the first `assert_type` fails. Mypy could choose to instead infer `42` as being of type `int`, which would make the second assert fail instead.

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -3918,6 +3918,12 @@ def visit_assert_type_expr(self, expr: AssertTypeExpr) -> Type:
             always_allow_any=True,
         )
         target_type = expr.type
+        proper_source_type = get_proper_type(source_type)
+        if (
+            isinstance(proper_source_type, mypy.types.Instance)
+            and proper_source_type.last_known_value is not None
+        ):
+            source_type = proper_source_type.last_known_value
         if not is_same_type(source_type, target_type):
             if not self.chk.in_checked_function():
                 self.msg.note(


Test patch:
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -937,6 +937,8 @@ reveal_type(returned)  # N: Revealed type is "builtins.int"
 assert_type(a, str)  # E: Expression is of type "int", not "str"
 assert_type(a, Any)  # E: Expression is of type "int", not "Any"
 assert_type(a, Literal[1])  # E: Expression is of type "int", not "Literal[1]"
+assert_type(42, Literal[42])
+assert_type(42, int)  # E: Expression is of type "Literal[42]", not "int"
 [builtins fixtures/tuple.pyi]
 
 [case testAssertTypeGeneric]