Repository: python/mypy

Issue:
Support 3.8 positional-only arguments
PEP 570 (https://www.python.org/dev/peps/pep-0570/) introduces positional-only arguments for 3.8, which we should support before the 3.8 release.


Discussion:
I'm interested in working on this but might not find time for it, so if anybody else wants to, go for it.
I'm interested in working on this as well. I've peeked around the argument handling code, and this seems like an issue that an outside contributor should be able to work out.

@JelleZijlstra, are you okay with me taking a pass at this this week/weekend and reaching out to you if I encounter anything tricky? 
Go for it! Maybe I'll take a stab at the walrus operator instead.
Yay!


Resolving patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -455,9 +455,11 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
         lineno = n.lineno
         args = self.transform_args(n.args, lineno, no_type_check=no_type_check)
 
+        posonlyargs = [arg.arg for arg in getattr(n.args, "posonlyargs", [])]
         arg_kinds = [arg.kind for arg in args]
         arg_names = [arg.variable.name() for arg in args]  # type: List[Optional[str]]
-        arg_names = [None if argument_elide_name(name) else name for name in arg_names]
+        arg_names = [None if argument_elide_name(name) or name in posonlyargs else name
+                     for name in arg_names]
         if special_function_elide_names(n.name):
             arg_names = [None] * len(arg_names)
         arg_types = []  # type: List[Optional[Type]]
@@ -584,7 +586,7 @@ def transform_args(self,
                        ) -> List[Argument]:
         new_args = []
         names = []  # type: List[ast3.arg]
-        args_args = args.args
+        args_args = getattr(args, "posonlyargs", []) + args.args
         args_defaults = args.defaults
         num_no_defaults = len(args_args) - len(args_defaults)
         # positional arguments without defaults


Test patch:
diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py
--- a/mypy/test/testcheck.py
+++ b/mypy/test/testcheck.py
@@ -90,6 +90,11 @@
 if sys.version_info >= (3, 8):
     typecheck_files.append('check-38.test')
 
+    # Remove this once Travis supports 3.8.0a4+.
+    if sys.version_info >= (3, 8, 0, "alpha", 4) and os.environ.get("TRAVIS"):
+        import warnings
+        warnings.warn("PEP 570 tests in check-38.test can be unskipped! 🎉")
+
 # Special tests for platforms with case-insensitive filesystems.
 if sys.platform in ('darwin', 'win32'):
     typecheck_files.append('check-modules-case.test')
diff --git a/test-data/unit/check-38.test b/test-data/unit/check-38.test
--- a/test-data/unit/check-38.test
+++ b/test-data/unit/check-38.test
@@ -106,3 +106,78 @@ def g(x: int): ...
     /
     0  # type: ignore
 )  # type: ignore  # E: unused 'type: ignore' comment
+
+-- Unskip the following PEP 570 tests once Travis supports 3.8.0a4 (or greater).
+
+[case testPEP570ArgTypesMissing-skip]
+# flags: --disallow-untyped-defs
+def f(arg, /) -> None: ...  # E: Function is missing a type annotation for one or more arguments
+
+[case testPEP570ArgTypesBadDefault-skip]
+def f(arg: int = "ERROR", /) -> None: ...  # E: Incompatible default for argument "arg" (default has type "str", argument has type "int")
+
+[case testPEP570ArgTypesDefault-skip]
+def f(arg: int = 0, /) -> None:
+    reveal_type(arg)  # E: Revealed type is 'builtins.int'
+
+[case testPEP570ArgTypesRequired-skip]
+def f(arg: int, /) -> None:
+    reveal_type(arg)  # E: Revealed type is 'builtins.int'
+
+[case testPEP570Required-skip]
+def f(arg: int, /) -> None: ...  # N: "f" defined here
+f(1)
+f("ERROR")  # E: Argument 1 to "f" has incompatible type "str"; expected "int"
+f(arg=1)  # E: Unexpected keyword argument "arg" for "f"
+f(arg="ERROR")  # E: Unexpected keyword argument "arg" for "f"
+
+[case testPEP570Default-skip]
+def f(arg: int = 0, /) -> None: ...  # N: "f" defined here
+f()
+f(1)
+f("ERROR")  # E: Argument 1 to "f" has incompatible type "str"; expected "int"
+f(arg=1)  # E: Unexpected keyword argument "arg" for "f"
+f(arg="ERROR")  # E: Unexpected keyword argument "arg" for "f"
+
+[case testPEP570Calls-skip]
+def f(p, /, p_or_kw, *, kw) -> None: ...  # N: "f" defined here
+f(0, 0, 0)  # E: Too many positional arguments for "f"
+f(0, 0, kw=0)
+f(0, p_or_kw=0, kw=0)
+f(p=0, p_or_kw=0, kw=0)  # E: Unexpected keyword argument "p" for "f"
+
+[case testPEP570Signatures1-skip]
+def f(p1: bytes, p2: float, /, p_or_kw: int, *, kw: str) -> None:
+    reveal_type(p1)  # E: Revealed type is 'builtins.bytes'
+    reveal_type(p2)  # E: Revealed type is 'builtins.float'
+    reveal_type(p_or_kw)  # E: Revealed type is 'builtins.int'
+    reveal_type(kw)  # E: Revealed type is 'builtins.str'
+
+[case testPEP570Signatures2-skip]
+def f(p1: bytes, p2: float = 0.0, /, p_or_kw: int = 0, *, kw: str) -> None:
+    reveal_type(p1)  # E: Revealed type is 'builtins.bytes'
+    reveal_type(p2)  # E: Revealed type is 'builtins.float'
+    reveal_type(p_or_kw)  # E: Revealed type is 'builtins.int'
+    reveal_type(kw)  # E: Revealed type is 'builtins.str'
+
+[case testPEP570Signatures3-skip]
+def f(p1: bytes, p2: float = 0.0, /, *, kw: int) -> None:
+    reveal_type(p1)  # E: Revealed type is 'builtins.bytes'
+    reveal_type(p2)  # E: Revealed type is 'builtins.float'
+    reveal_type(kw)  # E: Revealed type is 'builtins.int'
+
+[case testPEP570Signatures4-skip]
+def f(p1: bytes, p2: int = 0, /) -> None:
+    reveal_type(p1)  # E: Revealed type is 'builtins.bytes'
+    reveal_type(p2)  # E: Revealed type is 'builtins.int'
+
+[case testPEP570Signatures5-skip]
+def f(p1: bytes, p2: float, /, p_or_kw: int) -> None:
+    reveal_type(p1)  # E: Revealed type is 'builtins.bytes'
+    reveal_type(p2)  # E: Revealed type is 'builtins.float'
+    reveal_type(p_or_kw)  # E: Revealed type is 'builtins.int'
+
+[case testPEP570Signatures6-skip]
+def f(p1: bytes, p2: float, /) -> None:
+    reveal_type(p1)  # E: Revealed type is 'builtins.bytes'
+    reveal_type(p2)  # E: Revealed type is 'builtins.float'