Repository: python/mypy
Issue category: Bug Report

Issue:
(🐞) Wrong column number for "Invalid location for ParamSpec"
```py
from typing import List, ParamSpec

P = ParamSpec('P')

def foo(x: List[P]) -> None: ...
```
```
test.py:5:13: error: Invalid location for ParamSpec "P"  [misc]
test.py:5:18: note: You can use ParamSpec as the first argument to Callable, e.g., 'Callable[P, int]'
Found 2 errors in 1 file (checked 1 source file)
```

Column 13 is the `L` in `List`. The error and the note don't match up.

Notice that the error and the note specify different column numbers.

An internal error with the correct column number is generated, but is removed as a duplicate.


Hints:
This has changed in master I think:
```
test.py:5:12: error: Invalid location for ParamSpec "P"  [misc]
test.py:5:12: note: You can use ParamSpec as the first argument to Callable, e.g., 'Callable[P, int]'
Found 1 error in 1 file (checked 1 source file)
```

They have the same column now (still not on the `ParamSpec` though)

Patch:
diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py
index 646bb28a3b6e..435abb78ca43 100644
--- a/mypy/semanal_typeargs.py
+++ b/mypy/semanal_typeargs.py
@@ -148,17 +148,18 @@ def validate_args(
         is_error = False
         is_invalid = False
         for (i, arg), tvar in zip(enumerate(args), type_vars):
+            context = ctx if arg.line < 0 else arg
             if isinstance(tvar, TypeVarType):
                 if isinstance(arg, ParamSpecType):
                     is_invalid = True
                     self.fail(
                         INVALID_PARAM_SPEC_LOCATION.format(format_type(arg, self.options)),
-                        ctx,
+                        context,
                         code=codes.VALID_TYPE,
                     )
                     self.note(
                         INVALID_PARAM_SPEC_LOCATION_NOTE.format(arg.name),
-                        ctx,
+                        context,
                         code=codes.VALID_TYPE,
                     )
                     continue
@@ -167,7 +168,7 @@ def validate_args(
                     self.fail(
                         f"Cannot use {format_type(arg, self.options)} for regular type variable,"
                         " only for ParamSpec",
-                        ctx,
+                        context,
                         code=codes.VALID_TYPE,
                     )
                     continue
@@ -182,13 +183,15 @@ def validate_args(
                             is_error = True
                             self.fail(
                                 message_registry.INVALID_TYPEVAR_AS_TYPEARG.format(arg.name, name),
-                                ctx,
+                                context,
                                 code=codes.TYPE_VAR,
                             )
                             continue
                     else:
                         arg_values = [arg]
-                    if self.check_type_var_values(name, arg_values, tvar.name, tvar.values, ctx):
+                    if self.check_type_var_values(
+                        name, arg_values, tvar.name, tvar.values, context
+                    ):
                         is_error = True
                 # Check against upper bound. Since it's object the vast majority of the time,
                 # add fast path to avoid a potentially slow subtype check.
@@ -209,7 +212,7 @@ def validate_args(
                             name,
                             format_type(upper_bound, self.options),
                         ),
-                        ctx,
+                        context,
                         code=codes.TYPE_VAR,
                     )
             elif isinstance(tvar, ParamSpecType):
@@ -220,7 +223,7 @@ def validate_args(
                     self.fail(
                         "Can only replace ParamSpec with a parameter types list or"
                         f" another ParamSpec, got {format_type(arg, self.options)}",
-                        ctx,
+                        context,
                         code=codes.VALID_TYPE,
                     )
         if is_invalid:


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
index 5ce80faaee18..a3d35da15107 100644
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -6112,8 +6112,8 @@ A = G
 x: A[B[int]] # E
 B = G
 [out]
-main:8:4: error: Type argument "G[int]" of "G" must be a subtype of "str"
-main:8:6: error: Type argument "int" of "G" must be a subtype of "str"
+main:8:6: error: Type argument "G[int]" of "G" must be a subtype of "str"
+main:8:8: error: Type argument "int" of "G" must be a subtype of "str"
 
 [case testExtremeForwardReferencing]
 from typing import TypeVar, Generic
diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test
index 44524b9df943..79a2f31b574b 100644
--- a/test-data/unit/check-columns.test
+++ b/test-data/unit/check-columns.test
@@ -310,9 +310,18 @@ T = TypeVar('T', int, str)
 class C(Generic[T]):
     pass
 
-def f(c: C[object]) -> None: pass # E:10: Value of type variable "T" of "C" cannot be "object"
+def f(c: C[object]) -> None: pass # E:12: Value of type variable "T" of "C" cannot be "object"
 (C[object]()) # E:2: Value of type variable "T" of "C" cannot be "object"
 
+[case testColumnInvalidLocationForParamSpec]
+from typing import List
+from typing_extensions import ParamSpec
+
+P = ParamSpec('P')
+def foo(x: List[P]): pass  # E:17: Invalid location for ParamSpec "P" \
+                           # N:17: You can use ParamSpec as the first argument to Callable, e.g., "Callable[P, int]"
+[builtins fixtures/list.pyi]
+
 [case testColumnSyntaxErrorInTypeAnnotation]
 if int():
     def f(x # type: int,
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
index b8cc0422b749..5791b9c471d5 100644
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -671,7 +671,7 @@ reveal_type(a) # N: Revealed type is "other.array[Any, other.dtype[builtins.floa
 [out]
 main:3: error: Type argument "float" of "Array" must be a subtype of "generic"  [type-var]
     a: other.Array[float]
-       ^
+                   ^
 [file other.py]
 from typing import Any, Generic, TypeVar
 
diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test
index 784b9db9f66e..81b0066dbf81 100644
--- a/test-data/unit/check-newsemanal.test
+++ b/test-data/unit/check-newsemanal.test
@@ -1666,8 +1666,8 @@ T = TypeVar('T', bound=int)
 class C(Generic[T]): pass
 class C2(Generic[T]): pass
 
-A = C[str] # E: Type argument "str" of "C" must be a subtype of "int" \
-           # E: Value of type variable "T" of "C" cannot be "str"
+A = C[str] # E: Value of type variable "T" of "C" cannot be "str" \
+           # E: Type argument "str" of "C" must be a subtype of "int"
 B = Union[C[str], int] # E: Type argument "str" of "C" must be a subtype of "int"
 S = TypeVar('S', bound=C[str]) # E: Type argument "str" of "C" must be a subtype of "int"
 U = TypeVar('U', C[str], str) # E: Type argument "str" of "C" must be a subtype of "int"