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 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)