Repository: python/mypy

Issue:
(🐞) Positional only parameter syntax doesn't show in overload error messages
```py
from typing import overload

@overload
def foo(a: int, /): ...
@overload
def foo(a: str): ...
def foo(a): ...


foo(a=1)
# main.py:10: error: No overload variant of "foo" matches argument type "int"
# main.py:10: note: Possible overload variants:
# main.py:10: note:     def foo(a, int) -> Any
# main.py:10: note:     def foo(a: str) -> Any
```
The error message is now incorrect.

[playground](https://mypy-play.net/?mypy=latest&python=3.10&gist=dfafdca4f2bc9e23a4b004ffeea72f80)


Discussion:

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -2377,6 +2377,7 @@ def [T <: int] f(self, x: int, y: T) -> None
     """
     s = ""
     asterisk = False
+    slash = False
     for i in range(len(tp.arg_types)):
         if s:
             s += ", "
@@ -2394,6 +2395,17 @@ def [T <: int] f(self, x: int, y: T) -> None
         s += format_type_bare(tp.arg_types[i])
         if tp.arg_kinds[i].is_optional():
             s += " = ..."
+        if (
+            not slash
+            and tp.arg_kinds[i].is_positional()
+            and name is None
+            and (
+                i == len(tp.arg_types) - 1
+                or (tp.arg_names[i + 1] is not None or not tp.arg_kinds[i + 1].is_positional())
+            )
+        ):
+            s += ", /"
+            slash = True
 
     # If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list
     if (
@@ -2401,16 +2413,11 @@ def [T <: int] f(self, x: int, y: T) -> None
         and tp.definition.name is not None
         and hasattr(tp.definition, "arguments")
     ):
-        definition_args = [arg.variable.name for arg in tp.definition.arguments]
-        if (
-            definition_args
-            and tp.arg_names != definition_args
-            and len(definition_args) > 0
-            and definition_args[0]
-        ):
+        definition_arg_names = [arg.variable.name for arg in tp.definition.arguments]
+        if len(definition_arg_names) > len(tp.arg_names) and definition_arg_names[0]:
             if s:
                 s = ", " + s
-            s = definition_args[0] + s
+            s = definition_arg_names[0] + s
         s = f"{tp.definition.name}({s})"
     elif tp.name:
         first_arg = tp.def_extras.get("first_arg")


Test patch:
diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test
--- a/test-data/unit/check-class-namedtuple.test
+++ b/test-data/unit/check-class-namedtuple.test
@@ -585,8 +585,8 @@ class Base(NamedTuple):
         reveal_type(self[T])  # N: Revealed type is "builtins.int" \
                               # E: No overload variant of "__getitem__" of "tuple" matches argument type "object" \
                               # N: Possible overload variants: \
-                              # N:     def __getitem__(self, int) -> int \
-                              # N:     def __getitem__(self, slice) -> Tuple[int, ...]
+                              # N:     def __getitem__(self, int, /) -> int \
+                              # N:     def __getitem__(self, slice, /) -> Tuple[int, ...]
         return self.x
     def bad_override(self) -> int:
         return self.x
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -1897,12 +1897,12 @@ class B(A):
 [out]
 tmp/foo.pyi:5: error: Signature of "__add__" incompatible with supertype "A"
 tmp/foo.pyi:5: note:      Superclass:
-tmp/foo.pyi:5: note:          def __add__(self, int) -> int
+tmp/foo.pyi:5: note:          def __add__(self, int, /) -> int
 tmp/foo.pyi:5: note:      Subclass:
 tmp/foo.pyi:5: note:          @overload
-tmp/foo.pyi:5: note:          def __add__(self, int) -> int
+tmp/foo.pyi:5: note:          def __add__(self, int, /) -> int
 tmp/foo.pyi:5: note:          @overload
-tmp/foo.pyi:5: note:          def __add__(self, str) -> str
+tmp/foo.pyi:5: note:          def __add__(self, str, /) -> str
 tmp/foo.pyi:5: note: Overloaded operator methods cannot have wider argument types in overrides
 
 [case testOperatorMethodOverrideWideningArgumentType]
@@ -2012,16 +2012,16 @@ class B(A):
 tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A"
 tmp/foo.pyi:8: note:      Superclass:
 tmp/foo.pyi:8: note:          @overload
-tmp/foo.pyi:8: note:          def __add__(self, int) -> A
+tmp/foo.pyi:8: note:          def __add__(self, int, /) -> A
 tmp/foo.pyi:8: note:          @overload
-tmp/foo.pyi:8: note:          def __add__(self, str) -> A
+tmp/foo.pyi:8: note:          def __add__(self, str, /) -> A
 tmp/foo.pyi:8: note:      Subclass:
 tmp/foo.pyi:8: note:          @overload
-tmp/foo.pyi:8: note:          def __add__(self, int) -> A
+tmp/foo.pyi:8: note:          def __add__(self, int, /) -> A
 tmp/foo.pyi:8: note:          @overload
-tmp/foo.pyi:8: note:          def __add__(self, str) -> A
+tmp/foo.pyi:8: note:          def __add__(self, str, /) -> A
 tmp/foo.pyi:8: note:          @overload
-tmp/foo.pyi:8: note:          def __add__(self, type) -> A
+tmp/foo.pyi:8: note:          def __add__(self, type, /) -> A
 tmp/foo.pyi:8: note: Overloaded operator methods cannot have wider argument types in overrides
 
 [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder]
diff --git a/test-data/unit/check-ctypes.test b/test-data/unit/check-ctypes.test
--- a/test-data/unit/check-ctypes.test
+++ b/test-data/unit/check-ctypes.test
@@ -15,8 +15,8 @@ a[1] = ctypes.c_int(42)
 a[2] = MyCInt(42)
 a[3] = b"bytes"  # E: No overload variant of "__setitem__" of "Array" matches argument types "int", "bytes" \
                  # N: Possible overload variants: \
-                 # N:     def __setitem__(self, int, Union[c_int, int]) -> None \
-                 # N:     def __setitem__(self, slice, List[Union[c_int, int]]) -> None
+                 # N:     def __setitem__(self, int, Union[c_int, int], /) -> None \
+                 # N:     def __setitem__(self, slice, List[Union[c_int, int]], /) -> None
 for x in a:
     reveal_type(x)  # N: Revealed type is "builtins.int"
 [builtins fixtures/floatdict.pyi]
@@ -38,13 +38,13 @@ reveal_type(mya[1:3])  # N: Revealed type is "builtins.list[__main__.MyCInt]"
 mya[0] = 42
 mya[1] = ctypes.c_int(42)  # E: No overload variant of "__setitem__" of "Array" matches argument types "int", "c_int" \
                            # N: Possible overload variants: \
-                           # N:     def __setitem__(self, int, Union[MyCInt, int]) -> None \
-                           # N:     def __setitem__(self, slice, List[Union[MyCInt, int]]) -> None
+                           # N:     def __setitem__(self, int, Union[MyCInt, int], /) -> None \
+                           # N:     def __setitem__(self, slice, List[Union[MyCInt, int]], /) -> None
 mya[2] = MyCInt(42)
 mya[3] = b"bytes"  # E: No overload variant of "__setitem__" of "Array" matches argument types "int", "bytes" \
                    # N: Possible overload variants: \
-                   # N:     def __setitem__(self, int, Union[MyCInt, int]) -> None \
-                   # N:     def __setitem__(self, slice, List[Union[MyCInt, int]]) -> None
+                   # N:     def __setitem__(self, int, Union[MyCInt, int], /) -> None \
+                   # N:     def __setitem__(self, slice, List[Union[MyCInt, int]], /) -> None
 for myx in mya:
     reveal_type(myx)  # N: Revealed type is "__main__.MyCInt"
 
@@ -71,8 +71,8 @@ mya[1] = ctypes.c_uint(42)
 mya[2] = MyCInt(42)
 mya[3] = b"bytes"  # E: No overload variant of "__setitem__" of "Array" matches argument types "int", "bytes" \
                    # N: Possible overload variants: \
-                   # N:     def __setitem__(self, int, Union[MyCInt, int, c_uint]) -> None \
-                   # N:     def __setitem__(self, slice, List[Union[MyCInt, int, c_uint]]) -> None
+                   # N:     def __setitem__(self, int, Union[MyCInt, int, c_uint], /) -> None \
+                   # N:     def __setitem__(self, slice, List[Union[MyCInt, int, c_uint]], /) -> None
 for myx in mya:
     reveal_type(myx)  # N: Revealed type is "Union[__main__.MyCInt, builtins.int]"
 [builtins fixtures/floatdict.pyi]
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
@@ -857,8 +857,8 @@ a[b]
 a[c]
 a[1]  # E: No overload variant of "__getitem__" of "A" matches argument type "int" \
       # N: Possible overload variants: \
-      # N:     def __getitem__(self, B) -> int \
-      # N:     def __getitem__(self, C) -> str
+      # N:     def __getitem__(self, B, /) -> int \
+      # N:     def __getitem__(self, C, /) -> str
 
 i, s = None, None  # type: (int, str)
 if int():
diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test
--- a/test-data/unit/check-narrowing.test
+++ b/test-data/unit/check-narrowing.test
@@ -436,8 +436,8 @@ else:
 weird_mixture: Union[KeyedTypedDict, KeyedNamedTuple]
 if weird_mixture["key"] is Key.B:       # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \
                                         # N: Possible overload variants: \
-                                        # N:     def __getitem__(self, int) -> Literal[Key.C] \
-                                        # N:     def __getitem__(self, slice) -> Tuple[Literal[Key.C], ...]
+                                        # N:     def __getitem__(self, int, /) -> Literal[Key.C] \
+                                        # N:     def __getitem__(self, slice, /) -> Tuple[Literal[Key.C], ...]
     reveal_type(weird_mixture)          # N: Revealed type is "Union[TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}), Tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]]"
 else:
     reveal_type(weird_mixture)          # N: Revealed type is "Union[TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}), Tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]]"
diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test
--- a/test-data/unit/check-overloading.test
+++ b/test-data/unit/check-overloading.test
@@ -906,8 +906,8 @@ B() < B()
 A() < object() # E: Unsupported operand types for < ("A" and "object")
 B() < object() # E: No overload variant of "__lt__" of "B" matches argument type "object" \
                # N: Possible overload variants: \
-               # N:     def __lt__(self, B) -> int \
-               # N:     def __lt__(self, A) -> int
+               # N:     def __lt__(self, B, /) -> int \
+               # N:     def __lt__(self, A, /) -> int
 
 [case testOverloadedForwardMethodAndCallingReverseMethod]
 from foo import *
@@ -925,8 +925,8 @@ A() + 1
 A() + B()
 A() + '' # E: No overload variant of "__add__" of "A" matches argument type "str" \
          # N: Possible overload variants: \
-         # N:     def __add__(self, A) -> int \
-         # N:     def __add__(self, int) -> int
+         # N:     def __add__(self, A, /) -> int \
+         # N:     def __add__(self, int, /) -> int
 
 [case testOverrideOverloadSwapped]
 from foo import *
@@ -4738,12 +4738,12 @@ reveal_type(actually_b + Other())               # Note
 [out]
 main:12: error: Signature of "__add__" incompatible with supertype "A"
 main:12: note:      Superclass:
-main:12: note:          def __add__(self, A) -> A
+main:12: note:          def __add__(self, A, /) -> A
 main:12: note:      Subclass:
 main:12: note:          @overload
-main:12: note:          def __add__(self, Other) -> B
+main:12: note:          def __add__(self, Other, /) -> B
 main:12: note:          @overload
-main:12: note:          def __add__(self, A) -> A
+main:12: note:          def __add__(self, A, /) -> A
 main:12: note: Overloaded operator methods cannot have wider argument types in overrides
 main:32: note: Revealed type is "__main__.Other"
 
@@ -6494,6 +6494,23 @@ def foo(x: List[T]) -> str: ...
 def foo(x: Sequence[int]) -> int: ...
 [builtins fixtures/list.pyi]
 
+# Also see `check-python38.test` for similar tests with `/` args:
+[case testOverloadPositionalOnlyErrorMessageOldStyle]
+from typing import overload
+
+@overload
+def foo(__a: int): ...
+@overload
+def foo(a: str): ...
+def foo(a): ...
+
+foo(a=1)
+[out]
+main:9: error: No overload variant of "foo" matches argument type "int"
+main:9: note: Possible overload variants:
+main:9: note:     def foo(int, /) -> Any
+main:9: note:     def foo(a: str) -> Any
+
 [case testOverloadUnionGenericBounds]
 from typing import overload, TypeVar, Sequence, Union
 
diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test
--- a/test-data/unit/check-protocols.test
+++ b/test-data/unit/check-protocols.test
@@ -2896,12 +2896,12 @@ c: Lst3[Str]
 f(Lst3(c))  # E: Argument 1 to "f" has incompatible type "Lst3[Lst3[Str]]"; expected "GetItem[GetItem[Str]]" \
 # N: Following member(s) of "Lst3[Lst3[Str]]" have conflicts: \
 # N:     Expected:                  \
-# N:         def __getitem__(self, int) -> GetItem[Str] \
+# N:         def __getitem__(self, int, /) -> GetItem[Str] \
 # N:     Got:                       \
 # N:         @overload              \
-# N:         def __getitem__(self, slice) -> Lst3[Lst3[Str]] \
+# N:         def __getitem__(self, slice, /) -> Lst3[Lst3[Str]] \
 # N:         @overload              \
-# N:         def __getitem__(self, bool) -> Lst3[Str]
+# N:         def __getitem__(self, bool, /) -> Lst3[Str]
 
 [builtins fixtures/list.pyi]
 [typing fixtures/typing-full.pyi]
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
@@ -576,3 +576,96 @@ class Bar:
     def f(self, a: Optional[str] = None, /, *, b: bool = False) -> None:
         ...
 [builtins fixtures/bool.pyi]
+
+[case testOverloadPositionalOnlyErrorMessage]
+from typing import overload
+
+@overload
+def foo(a: int, /): ...
+@overload
+def foo(a: str): ...
+def foo(a): ...
+
+foo(a=1)
+[out]
+main:9: error: No overload variant of "foo" matches argument type "int"
+main:9: note: Possible overload variants:
+main:9: note:     def foo(int, /) -> Any
+main:9: note:     def foo(a: str) -> Any
+
+[case testOverloadPositionalOnlyErrorMessageAllTypes]
+from typing import overload
+
+@overload
+def foo(a: int, /, b: int, *, c: int): ...
+@overload
+def foo(a: str, b: int, *, c: int): ...
+def foo(a, b, *, c): ...
+
+foo(a=1)
+[out]
+main:9: error: No overload variant of "foo" matches argument type "int"
+main:9: note: Possible overload variants:
+main:9: note:     def foo(int, /, b: int, *, c: int) -> Any
+main:9: note:     def foo(a: str, b: int, *, c: int) -> Any
+
+[case testOverloadPositionalOnlyErrorMessageMultiplePosArgs]
+from typing import overload
+
+@overload
+def foo(a: int, b: int, c: int, /, d: str): ...
+@overload
+def foo(a: str, b: int, c: int, d: str): ...
+def foo(a, b, c, d): ...
+
+foo(a=1)
+[out]
+main:9: error: No overload variant of "foo" matches argument type "int"
+main:9: note: Possible overload variants:
+main:9: note:     def foo(int, int, int, /, d: str) -> Any
+main:9: note:     def foo(a: str, b: int, c: int, d: str) -> Any
+
+[case testOverloadPositionalOnlyErrorMessageMethod]
+from typing import overload
+
+class Some:
+    @overload
+    def foo(self, __a: int): ...
+    @overload
+    def foo(self, a: float, /): ...
+    @overload
+    def foo(self, a: str): ...
+    def foo(self, a): ...
+
+Some().foo(a=1)
+[out]
+main:12: error: No overload variant of "foo" of "Some" matches argument type "int"
+main:12: note: Possible overload variants:
+main:12: note:     def foo(self, int, /) -> Any
+main:12: note:     def foo(self, float, /) -> Any
+main:12: note:     def foo(self, a: str) -> Any
+
+[case testOverloadPositionalOnlyErrorMessageClassMethod]
+from typing import overload
+
+class Some:
+    @overload
+    @classmethod
+    def foo(cls, __a: int): ...
+    @overload
+    @classmethod
+    def foo(cls, a: float, /): ...
+    @overload
+    @classmethod
+    def foo(cls, a: str): ...
+    @classmethod
+    def foo(cls, a): ...
+
+Some.foo(a=1)
+[builtins fixtures/classmethod.pyi]
+[out]
+main:16: error: No overload variant of "foo" of "Some" matches argument type "int"
+main:16: note: Possible overload variants:
+main:16: note:     def foo(cls, int, /) -> Any
+main:16: note:     def foo(cls, float, /) -> Any
+main:16: note:     def foo(cls, a: str) -> Any
diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test
--- a/test-data/unit/check-tuples.test
+++ b/test-data/unit/check-tuples.test
@@ -1228,8 +1228,8 @@ y = ""
 reveal_type(t[x])  # N: Revealed type is "Union[builtins.int, builtins.str]"
 t[y]  # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \
       # N: Possible overload variants: \
-      # N:     def __getitem__(self, int) -> object \
-      # N:     def __getitem__(self, slice) -> Tuple[object, ...]
+      # N:     def __getitem__(self, int, /) -> object \
+      # N:     def __getitem__(self, slice, /) -> Tuple[object, ...]
 
 [builtins fixtures/tuple.pyi]
 
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -478,9 +478,9 @@ fun2(a) # Error
 main:17: error: Argument 1 to "fun2" has incompatible type "A"; expected "StrIntMap"
 main:17: note: Following member(s) of "A" have conflicts:
 main:17: note:     Expected:
-main:17: note:         def __getitem__(self, str) -> int
+main:17: note:         def __getitem__(self, str, /) -> int
 main:17: note:     Got:
-main:17: note:         def __getitem__(self, str) -> object
+main:17: note:         def __getitem__(self, str, /) -> object
 
 [case testTypedDictWithSimpleProtocolInference]
 from typing_extensions import Protocol, TypedDict
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -660,8 +660,8 @@ a + 1
 [out]
 _testMapStr.py:4: error: No overload variant of "__add__" of "list" matches argument type "int"
 _testMapStr.py:4: note: Possible overload variants:
-_testMapStr.py:4: note:     def __add__(self, List[str]) -> List[str]
-_testMapStr.py:4: note:     def [_S] __add__(self, List[_S]) -> List[Union[_S, str]]
+_testMapStr.py:4: note:     def __add__(self, List[str], /) -> List[str]
+_testMapStr.py:4: note:     def [_S] __add__(self, List[_S], /) -> List[Union[_S, str]]
 
 [case testRelativeImport]
 import typing
@@ -805,8 +805,8 @@ t + 1
 [out]
 _program.py:3: error: No overload variant of "__add__" of "tuple" matches argument type "int"
 _program.py:3: note: Possible overload variants:
-_program.py:3: note:     def __add__(self, Tuple[str, ...]) -> Tuple[str, ...]
-_program.py:3: note:     def [_T] __add__(self, Tuple[_T, ...]) -> Tuple[Union[str, _T], ...]
+_program.py:3: note:     def __add__(self, Tuple[str, ...], /) -> Tuple[str, ...]
+_program.py:3: note:     def [_T] __add__(self, Tuple[_T, ...], /) -> Tuple[Union[str, _T], ...]
 
 [case testMultiplyTupleByIntegerReverse]
 n = 4
@@ -815,8 +815,8 @@ t + 1
 [out]
 _program.py:3: error: No overload variant of "__add__" of "tuple" matches argument type "int"
 _program.py:3: note: Possible overload variants:
-_program.py:3: note:     def __add__(self, Tuple[str, ...]) -> Tuple[str, ...]
-_program.py:3: note:     def [_T] __add__(self, Tuple[_T, ...]) -> Tuple[Union[str, _T], ...]
+_program.py:3: note:     def __add__(self, Tuple[str, ...], /) -> Tuple[str, ...]
+_program.py:3: note:     def [_T] __add__(self, Tuple[_T, ...], /) -> Tuple[Union[str, _T], ...]
 
 [case testDictWithKeywordArgs]
 from typing import Dict, Any, List
@@ -1099,8 +1099,8 @@ _testTypedDictGet.py:8: note: Revealed type is "builtins.str"
 _testTypedDictGet.py:9: note: Revealed type is "builtins.object"
 _testTypedDictGet.py:10: error: All overload variants of "get" of "Mapping" require at least one argument
 _testTypedDictGet.py:10: note: Possible overload variants:
-_testTypedDictGet.py:10: note:     def get(self, str) -> object
-_testTypedDictGet.py:10: note:     def [_T] get(self, str, default: object) -> object
+_testTypedDictGet.py:10: note:     def get(self, str, /) -> object
+_testTypedDictGet.py:10: note:     def [_T] get(self, str, /, default: object) -> object
 _testTypedDictGet.py:12: note: Revealed type is "builtins.object"
 
 [case testTypedDictMappingMethods]