From b7430ceaebb736d6ecb7bc8e9fa45e186d5ee233 Mon Sep 17 00:00:00 2001 From: Markus Schmaus Date: Tue, 12 May 2020 14:57:21 +0200 Subject: [PATCH 1/3] Allow attrs kw_only arguments at any position --- mypy/plugins/attrs.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index d1744f6a37ca..12675042aa57 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -358,7 +358,6 @@ def _analyze_class(ctx: 'mypy.plugin.ClassDefContext', # Check the init args for correct default-ness. Note: This has to be done after all the # attributes for all classes have been read, because subclasses can override parents. last_default = False - last_kw_only = False for i, attribute in enumerate(attributes): if not attribute.init: @@ -366,7 +365,6 @@ def _analyze_class(ctx: 'mypy.plugin.ClassDefContext', if attribute.kw_only: # Keyword-only attributes don't care whether they are default or not. - last_kw_only = True continue # If the issue comes from merging different classes, report it @@ -377,11 +375,6 @@ def _analyze_class(ctx: 'mypy.plugin.ClassDefContext', ctx.api.fail( "Non-default attributes not allowed after default attributes.", context) - if last_kw_only: - ctx.api.fail( - "Non keyword-only attributes are not allowed after a keyword-only attribute.", - context - ) last_default |= attribute.has_default return attributes @@ -626,7 +619,18 @@ def _make_frozen(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute] def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], adder: 'MethodAdder') -> None: """Generate an __init__ method for the attributes and add it to the class.""" - args = [attribute.argument(ctx) for attribute in attributes if attribute.init] + # Convert attributes to arguments with kw_only arguments at the end of + # the argument list + pos_args = [] + kw_only_args = [] + for attribute in attributes: + if not attribute.init: + continue + if attribute.kw_only: + kw_only_args.append(attribute.argument(ctx)) + else: + pos_args.append(attribute.argument(ctx)) + args = pos_args + kw_only_args if all( # We use getattr rather than instance checks because the variable.type # might be wrapped into a Union or some other type, but even non-Any From 0b2abea83f251c90365ab6c92917406d1c7241b0 Mon Sep 17 00:00:00 2001 From: Markus Schmaus Date: Tue, 12 May 2020 22:42:59 +0200 Subject: [PATCH 2/3] Fix tests for kw_only attrs --- runtests.py | 14 +++----------- test-data/unit/check-attr.test | 5 +++-- test-data/unit/fine-grained.test | 25 ------------------------- 3 files changed, 6 insertions(+), 38 deletions(-) diff --git a/runtests.py b/runtests.py index dec2c1b97cf5..e76501c7c21f 100755 --- a/runtests.py +++ b/runtests.py @@ -2,17 +2,9 @@ import subprocess from subprocess import Popen from os import system -from sys import argv, exit, platform, executable, version_info +from sys import argv, exit - -# Use the Python provided to execute the script, or fall back to a sane default -if version_info >= (3, 5, 0): - python_name = executable -else: - if platform == 'win32': - python_name = 'py -3' - else: - python_name = 'python3' +python_name = 'python' # Slow test suites CMDLINE = 'PythonCmdline' @@ -91,7 +83,7 @@ } # Stop run immediately if these commands fail -FAST_FAIL = ['self', 'lint'] +FAST_FAIL = ['self'] DEFAULT_COMMANDS = [cmd for cmd in cmds if cmd != 'mypyc-extra'] diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index 3bba82909684..28613454d2ff 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1070,11 +1070,12 @@ class A: a = attr.ib(default=0) @attr.s class B(A): - b = attr.ib() # E: Non keyword-only attributes are not allowed after a keyword-only attribute. + b = attr.ib() @attr.s class C: a = attr.ib(kw_only=True) - b = attr.ib(15) # E: Non keyword-only attributes are not allowed after a keyword-only attribute. + b = attr.ib(15) + [builtins fixtures/attr.pyi] [case testAttrsKwOnlyPy2] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 3ea6b31f379a..e098bc760f37 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -1023,31 +1023,6 @@ class A: == main:2: error: Unsupported left operand type for < ("B") -[case testAttrsUpdateKwOnly] -[file a.py] -import attr -@attr.s(kw_only=True) -class A: - a = attr.ib(15) # type: int -[file b.py] -from a import A -import attr -@attr.s(kw_only=True) -class B(A): - b = attr.ib("16") # type: str - -[file b.py.2] -from a import A -import attr -@attr.s() -class B(A): - b = attr.ib("16") # type: str -B(b="foo", a=7) -[builtins fixtures/attr.pyi] -[out] -== -b.py:5: error: Non keyword-only attributes are not allowed after a keyword-only attribute. - [case testAttrsUpdateBaseKwOnly] from b import B B(5) From e85b4f0404993a9d950b9586d250dddeb60a3938 Mon Sep 17 00:00:00 2001 From: Markus Schmaus Date: Sat, 16 May 2020 07:36:16 +0200 Subject: [PATCH 3/3] Undo changes for local testsing --- runtests.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/runtests.py b/runtests.py index e76501c7c21f..dec2c1b97cf5 100755 --- a/runtests.py +++ b/runtests.py @@ -2,9 +2,17 @@ import subprocess from subprocess import Popen from os import system -from sys import argv, exit +from sys import argv, exit, platform, executable, version_info -python_name = 'python' + +# Use the Python provided to execute the script, or fall back to a sane default +if version_info >= (3, 5, 0): + python_name = executable +else: + if platform == 'win32': + python_name = 'py -3' + else: + python_name = 'python3' # Slow test suites CMDLINE = 'PythonCmdline' @@ -83,7 +91,7 @@ } # Stop run immediately if these commands fail -FAST_FAIL = ['self'] +FAST_FAIL = ['self', 'lint'] DEFAULT_COMMANDS = [cmd for cmd in cmds if cmd != 'mypyc-extra']