Repository: python/mypy

Issue:
Clarify aliases vs variables
We have both type aliases and variables with types like ``Type[...]``, also there are module aliases and variables of type ``ModuleType``. I think we should document the difference in semantics and provide examples. (Also I think there are few minor things that should be fixed in mypy.) Here how I see things:

1. Variables with type ``Type[...]`` should be created by assignments with an explicit type annotations:
   ```python
   class A: ...
   tp: Type[A] = A
   ```
2. Aliases are created by assignments without an explicit type:
   ```python
   class A: ...
   Alias = A
   ```
3. The difference is that aliases are completely known statically and can be used in type context (annotations):
   ```python
   class A: ...
   class B: ...

   if random() > 0.5:
       Alias = A
   else:
       Alias = B  # I think this should be prohibited (there are two TODOs in semanal.py)

   tp: Type[object]
   if random() > 0.5:
       tp = A
   else:
       tp = B  # This is OK

   def fun1(x: Alias) -> None: ... # This is OK
   def fun2(x: tp) -> None: ... # Error
   ```
4. There is similar distinction between module aliases and variables with type ``types.ModuleType``:
   ```python
   import math, cmath
   from types import ModuleType

   mod: ModuleType = math  # variable
   m = math # Alias

   mod.sin # Error. ModuleType has no attribute 'sin'
   m.sin # OK
   ```
   and also
   ```python
   if random() > 0.5:
       m = math
   else:
       m = cmath  # Should be prohibited

   mod: ModuleType
   if random() > 0.5:
       mod = math
   else:
       mod = cmath # OK, note explicit annotation above
   ```
5. For modules, we could add better support later (a particular module is a literal type as in https://github.com/python/mypy/issues/3062 and binder can bind ``mod`` to ``Union[Literal[math], Literal[cmath]]`` after the above code).

@JukkaL Do we agree on semantics here? If yes, then I will make some minor changes in mypy and add this to documentation (maybe we also need to clarify this in PEP 484).


Discussion:
The current version of #3435 allows

```
if random() > 0.5:
    m = math
else:
    m = cmath
```

and infers `m` as `ModuleType` var (not module alias), even without the explicit prior annotation. I did it this way because of the concern that we could break currently-working code otherwise.

The behavior you propose here would actually be much simpler to implement, as it wouldn't require backtracking. It only requires that we never propagate a module alias to an already-typed variable, which is already implemented.
@carljm 
> The behavior you propose here would actually be much simpler to implement

Probably you are right. Even though this may flag some existing code, this would be actually right. Plus this can be easily fixed by adding explicit annotation - EIBTI.
@ilevkivskyi I mostly agree, though I'm not sure about using literal types for modules -- they feel different to me. Anyway, better types for modules should probably be discussed separately from the rest of the points. Feel free to open an issue for this -- I couldn't find an existing issue.

@carljm I agree with Ivan -- requiring an explicit annotation would be the right thing in case of a conditional module alias. This would be consistent with how mypy behaves in other contexts.
@JukkaL 
> I'm not sure about using literal types for modules -- they feel different to me. Anyway, better types for modules should probably be discussed separately from the rest of the points. Feel free to open an issue for this -- I couldn't find an existing issue.

Yes, I will open a separate issue. This was just a vague idea.
OK, opened a separate issue #3500  for point 5 of the list in original post.

EDIT: Corrected the issue number.
The "better types for modules" issue is #3500.

Resolving patch:
diff --git a/mypy/typeanal.py b/mypy/typeanal.py
--- a/mypy/typeanal.py
+++ b/mypy/typeanal.py
@@ -423,7 +423,8 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl
         # TODO: Move this message building logic to messages.py.
         notes = []  # type: List[str]
         if isinstance(sym.node, Var):
-            # TODO: add a link to alias docs, see #3494.
+            notes.append('See https://mypy.readthedocs.io/en/'
+                         'latest/common_issues.html#variables-vs-type-aliases')
             message = 'Variable "{}" is not valid as a type'
         elif isinstance(sym.node, (SYMBOL_FUNCBASE_TYPES, Decorator)):
             message = 'Function "{}" is not valid as a type'


Test patch:
diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test
--- a/test-data/unit/check-columns.test
+++ b/test-data/unit/check-columns.test
@@ -153,16 +153,22 @@ from typing import Iterable
 
 bad = 0
 
-def f(x: bad): # E:10: Variable "__main__.bad" is not valid as a type
-    y: bad # E:8: Variable "__main__.bad" is not valid as a type
+def f(x: bad): # E:10: Variable "__main__.bad" is not valid as a type \
+               # N:10: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
+    y: bad # E:8: Variable "__main__.bad" is not valid as a type \
+           # N:8: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 if int():
-    def g(x): # E:5: Variable "__main__.bad" is not valid as a type
+    def g(x): # E:5: Variable "__main__.bad" is not valid as a type \
+              # N:5: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
         # type: (bad) -> None
-        y = 0  # type: bad  # E:9: Variable "__main__.bad" is not valid as a type
+        y = 0  # type: bad  # E:9: Variable "__main__.bad" is not valid as a type \
+                            # N:9: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
-z: Iterable[bad] # E:13: Variable "__main__.bad" is not valid as a type
-h: bad[int] # E:4: Variable "__main__.bad" is not valid as a type
+z: Iterable[bad] # E:13: Variable "__main__.bad" is not valid as a type \
+                 # N:13: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
+h: bad[int] # E:4: Variable "__main__.bad" is not valid as a type \
+            # N:4: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testColumnInvalidType_python2]
 
@@ -171,11 +177,14 @@ from typing import Iterable
 bad = 0
 
 if int():
-    def g(x): # E:5: Variable "__main__.bad" is not valid as a type
+    def g(x): # E:5: Variable "__main__.bad" is not valid as a type \
+              # N:5: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
         # type: (bad) -> None
-        y = 0  # type: bad  # E:9: Variable "__main__.bad" is not valid as a type
+        y = 0  # type: bad  # E:9: Variable "__main__.bad" is not valid as a type \
+                            # N:9: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
-    z = ()  # type: Iterable[bad] # E:5: Variable "__main__.bad" is not valid as a type
+    z = ()  # type: Iterable[bad] # E:5: Variable "__main__.bad" is not valid as a type \
+                                  # N:5: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testColumnFunctionMissingTypeAnnotation]
 # flags: --disallow-untyped-defs
diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test
--- a/test-data/unit/check-custom-plugin.test
+++ b/test-data/unit/check-custom-plugin.test
@@ -498,10 +498,13 @@ Bad1 = non_declarative_base()
 Bad2 = Bad3 = declarative_base()
 
 class C1(Bad1): ...  # E: Variable "__main__.Bad1" is not valid as a type \
+                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases \
                      # E: Invalid base class "Bad1"
 class C2(Bad2): ...  # E: Variable "__main__.Bad2" is not valid as a type \
+                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases \
                      # E: Invalid base class "Bad2"
 class C3(Bad3): ...  # E: Variable "__main__.Bad3" is not valid as a type \
+                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases \
                      # E: Invalid base class "Bad3"
 [file mod.py]
 from typing import Generic, TypeVar
diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test
--- a/test-data/unit/check-errorcodes.test
+++ b/test-data/unit/check-errorcodes.test
@@ -250,7 +250,8 @@ x: f  # E: Function "__main__.f" is not valid as a type  [valid-type] \
 
 import sys
 y: sys  # E: Module "sys" is not valid as a type  [valid-type]
-z: y  # E: Variable "__main__.y" is not valid as a type  [valid-type]
+z: y  # E: Variable "__main__.y" is not valid as a type  [valid-type] \
+      # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 [builtins fixtures/tuple.pyi]
 
 [case testErrorCodeNeedTypeAnnotation]
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -985,9 +985,11 @@ class C:
         b = int  # E: Cannot assign multiple types to name "b" without an explicit "Type[...]" annotation
     if int():
         c = int
-    def f(self, x: a) -> None: pass  # E: Variable "__main__.C.a" is not valid as a type
+    def f(self, x: a) -> None: pass  # E: Variable "__main__.C.a" is not valid as a type \
+                                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
     def g(self, x: b) -> None: pass
-    def h(self, x: c) -> None: pass  # E: Variable "__main__.C.c" is not valid as a type
+    def h(self, x: c) -> None: pass  # E: Variable "__main__.C.c" is not valid as a type \
+                                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
     x: b
     reveal_type(x)  # N: Revealed type is 'Union[builtins.int, builtins.str]'
 [out]
diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test
--- a/test-data/unit/check-literal.test
+++ b/test-data/unit/check-literal.test
@@ -707,6 +707,7 @@ y: Foo[Foo]     # E: Literal[...] must have at least one parameter
 
 NotAType = 3
 def f() -> NotAType['also' + 'not' + 'a' + 'type']: ... # E: Variable "__main__.NotAType" is not valid as a type \
+                                                        # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases \
                                                         # E: Invalid type comment or annotation
 
 # Note: this makes us re-inspect the type (e.g. via '_patch_indirect_dependencies'
@@ -907,10 +908,12 @@ d2t = 3j
 
 a2: a2t
 reveal_type(a2)      # N: Revealed type is 'Any'
-b2: b2t              # E: Variable "__main__.b2t" is not valid as a type
+b2: b2t              # E: Variable "__main__.b2t" is not valid as a type \
+                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 c2: c2t
 reveal_type(c2)      # N: Revealed type is 'Any'
-d2: d2t              # E: Variable "__main__.d2t" is not valid as a type
+d2: d2t              # E: Variable "__main__.d2t" is not valid as a type \
+                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 [builtins fixtures/complex_tuple.pyi]
 [out]
 
@@ -949,8 +952,10 @@ c: [1, 2, 3]                    # E: Bracketed expression "[...]" is not valid a
 from typing_extensions import Literal
 at = Literal[{"a": 1, "b": 2}]  # E: Invalid type alias: expression is not a valid type
 bt = {"a": 1, "b": 2}
-a: at                           # E: Variable "__main__.at" is not valid as a type
-b: bt                           # E: Variable "__main__.bt" is not valid as a type
+a: at                           # E: Variable "__main__.at" is not valid as a type \
+                                # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
+b: bt                           # E: Variable "__main__.bt" is not valid as a type \
+                                # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 [builtins fixtures/dict.pyi]
 [out]
 
@@ -959,8 +964,10 @@ b: bt                           # E: Variable "__main__.bt" is not valid as a ty
 from typing_extensions import Literal
 at = Literal[{1, 2, 3}]         # E: Invalid type alias: expression is not a valid type
 bt = {1, 2, 3}
-a: at                           # E: Variable "__main__.at" is not valid as a type
-b: bt                           # E: Variable "__main__.bt" is not valid as a type
+a: at                           # E: Variable "__main__.at" is not valid as a type \
+                                # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
+b: bt                           # E: Variable "__main__.bt" is not valid as a type \
+                                # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 [builtins fixtures/set.pyi]
 [out]
 
@@ -2868,13 +2875,17 @@ d: Literal[3]
 # "3" wherever it's used and get the same behavior -- so maybe we do need to support
 # at least case "b" for consistency?
 a_wrap: Literal[4, a]  # E: Parameter 2 of Literal[...] is invalid \
-                       # E: Variable "__main__.a" is not valid as a type
+                       # E: Variable "__main__.a" is not valid as a type \
+                       # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 b_wrap: Literal[4, b]  # E: Parameter 2 of Literal[...] is invalid \
-                       # E: Variable "__main__.b" is not valid as a type
+                       # E: Variable "__main__.b" is not valid as a type \
+                       # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 c_wrap: Literal[4, c]  # E: Parameter 2 of Literal[...] is invalid \
-                       # E: Variable "__main__.c" is not valid as a type
+                       # E: Variable "__main__.c" is not valid as a type \
+                       # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 d_wrap: Literal[4, d]  # E: Parameter 2 of Literal[...] is invalid \
-                       # E: Variable "__main__.d" is not valid as a type
+                       # E: Variable "__main__.d" is not valid as a type \
+                       # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 [builtins fixtures/tuple.pyi]
 [out]
 
diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test
--- a/test-data/unit/check-python38.test
+++ b/test-data/unit/check-python38.test
@@ -238,10 +238,12 @@ def f(x: int = (c := 4)) -> int:
 
     # Just make sure we don't crash on this sort of thing.
     if NT := NamedTuple("NT", [("x", int)]):  # E: "int" not callable
-        z2: NT  # E: Variable "NT" is not valid as a type
+        z2: NT  # E: Variable "NT" is not valid as a type \
+                # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
     if Alias := int:
-        z3: Alias  # E: Variable "Alias" is not valid as a type
+        z3: Alias  # E: Variable "Alias" is not valid as a type \
+                   # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
     if (reveal_type(y9 := 3) and   # N: Revealed type is 'Literal[3]?'
             reveal_type(y9)):       # N: Revealed type is 'builtins.int'
diff --git a/test-data/unit/check-redefine.test b/test-data/unit/check-redefine.test
--- a/test-data/unit/check-redefine.test
+++ b/test-data/unit/check-redefine.test
@@ -276,7 +276,8 @@ def f() -> None:
     # NOTE: '"int" not callable' is due to test stubs
     y = TypeVar('y')  # E: Cannot redefine 'y' as a type variable \
                       # E: "int" not callable
-    def h(a: y) -> y: return a # E: Variable "y" is not valid as a type
+    def h(a: y) -> y: return a # E: Variable "y" is not valid as a type \
+                               # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testCannotRedefineVarAsModule]
 # flags: --allow-redefinition
diff --git a/test-data/unit/check-semanal-error.test b/test-data/unit/check-semanal-error.test
--- a/test-data/unit/check-semanal-error.test
+++ b/test-data/unit/check-semanal-error.test
@@ -57,6 +57,7 @@ A().foo(1)
 A().x = '' # E
 [out]
 main:3: error: Variable "__main__.X" is not valid as a type
+main:3: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 main:3: error: Invalid base class "X"
 main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int")
 
diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test
--- a/test-data/unit/check-type-aliases.test
+++ b/test-data/unit/check-type-aliases.test
@@ -99,8 +99,10 @@ T = TypeVar('T')
 A: Type[float] = int
 if int():
     A = float  # OK
-x: A  # E: Variable "__main__.A" is not valid as a type
-def bad(tp: A) -> None:  # E: Variable "__main__.A" is not valid as a type
+x: A  # E: Variable "__main__.A" is not valid as a type \
+      # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
+def bad(tp: A) -> None:  # E: Variable "__main__.A" is not valid as a type \
+                         # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
     pass
 
 Alias = int
diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test
--- a/test-data/unit/fine-grained.test
+++ b/test-data/unit/fine-grained.test
@@ -7951,14 +7951,18 @@ x = 1
 a.py:1: error: Name 'TypeVar' is not defined
 a.py:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import TypeVar")
 a.py:7: error: Variable "a.T" is not valid as a type
+a.py:7: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 a.py:10: error: Name 'bar' already defined on line 6
 a.py:11: error: Variable "a.T" is not valid as a type
+a.py:11: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 ==
 a.py:1: error: Name 'TypeVar' is not defined
 a.py:1: note: Did you forget to import it from "typing"? (Suggestion: "from typing import TypeVar")
 a.py:7: error: Variable "a.T" is not valid as a type
+a.py:7: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 a.py:10: error: Name 'bar' already defined on line 6
 a.py:11: error: Variable "a.T" is not valid as a type
+a.py:11: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testRefreshForWithTypeComment1]
 [file a.py]
@@ -8423,6 +8427,7 @@ B = func
 [out]
 ==
 main:5: error: Variable "b.B" is not valid as a type
+main:5: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testNamedTupleForwardFunctionIndirect]
 # flags: --ignore-missing-imports
@@ -8440,6 +8445,7 @@ B = func
 [out]
 ==
 main:5: error: Variable "a.A" is not valid as a type
+main:5: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testNamedTupleForwardFunctionIndirectReveal]
 # flags: --ignore-missing-imports
@@ -8467,8 +8473,10 @@ B = func
 [out]
 ==
 m.py:4: error: Variable "a.A" is not valid as a type
+m.py:4: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 ==
 m.py:4: error: Variable "a.A" is not valid as a type
+m.py:4: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 m.py:5: note: Revealed type is 'A?'
 m.py:7: note: Revealed type is 'A?'
 
@@ -8484,6 +8492,7 @@ B = int()
 [out]
 ==
 main:5: error: Variable "b.B" is not valid as a type
+main:5: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testAliasForwardFunctionIndirect]
 # flags: --ignore-missing-imports
@@ -8500,6 +8509,7 @@ B = func
 [out]
 ==
 main:5: error: Variable "a.A" is not valid as a type
+main:5: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testLiteralFineGrainedVarConversion]
 import mod
diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test
--- a/test-data/unit/merge.test
+++ b/test-data/unit/merge.test
@@ -779,6 +779,7 @@ foo: int
 x: foo[A]
 [out]
 tmp/target.py:4: error: Variable "target.foo" is not valid as a type
+tmp/target.py:4: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 ## target
 NameExpr:3: builtins.int<0>
 NameExpr:4: foo?[target.A<1>]
diff --git a/test-data/unit/pythoneval-asyncio.test b/test-data/unit/pythoneval-asyncio.test
--- a/test-data/unit/pythoneval-asyncio.test
+++ b/test-data/unit/pythoneval-asyncio.test
@@ -502,3 +502,4 @@ def bad(arg: P) -> T:
 [out]
 _program.py:8: note: Revealed type is 'def [T] (arg: P?) -> T`-1'
 _program.py:12: error: Variable "_testForwardRefToBadAsyncShouldNotCrash_newsemanal.P" is not valid as a type
+_program.py:12: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test
--- a/test-data/unit/semanal-errors.test
+++ b/test-data/unit/semanal-errors.test
@@ -137,6 +137,7 @@ z = 0 # type: x
 main:5: error: Function "__main__.f" is not valid as a type
 main:5: note: Perhaps you need "Callable[...]" or a callback protocol?
 main:6: error: Variable "__main__.x" is not valid as a type
+main:6: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testGlobalVarRedefinition]
 import typing
@@ -802,7 +803,8 @@ cast([int, str], None)   # E: Bracketed expression "[...]" is not valid as a typ
 
 from typing import cast
 x = 0
-cast(x, None)        # E: Variable "__main__.x" is not valid as a type
+cast(x, None)        # E: Variable "__main__.x" is not valid as a type \
+                     # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 cast(t, None)        # E: Name 't' is not defined
 cast(__builtins__.x, None) # E: Name '__builtins__.x' is not defined
 [out]
@@ -897,7 +899,8 @@ main:4: error: Type cannot be declared in assignment to non-self attribute
 from typing import TypeVar, Generic
 t = TypeVar('t')
 class A(Generic[t]): pass
-A[TypeVar] # E: Variable "typing.TypeVar" is not valid as a type
+A[TypeVar] # E: Variable "typing.TypeVar" is not valid as a type \
+           # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 [out]
 
 [case testInvalidTypeInTypeApplication2]
diff --git a/test-data/unit/semanal-typealiases.test b/test-data/unit/semanal-typealiases.test
--- a/test-data/unit/semanal-typealiases.test
+++ b/test-data/unit/semanal-typealiases.test
@@ -404,13 +404,15 @@ MypyFile:1(
 
 import typing
 A = [int, str]
-a = 1 # type: A  # E: Variable "__main__.A" is not valid as a type
+a = 1 # type: A  # E: Variable "__main__.A" is not valid as a type \
+                 # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testCantUseStringLiteralAsTypeAlias]
 
 from typing import Union
 A = 'Union[int, str]'
-a = 1 # type: A  # E: Variable "__main__.A" is not valid as a type
+a = 1 # type: A  # E: Variable "__main__.A" is not valid as a type \
+                 # N: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
 
 [case testStringLiteralTypeAsAliasComponent]
 from typing import Union