From a709e114e6540490dc2c2249f096e9998b0b9fd8 Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 12:09:14 -0400 Subject: [PATCH 01/30] simulate a Call to Base.__init_subclass__ --- mypy/checker.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index deb096c1c479..ca3b2337bd7d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -26,7 +26,7 @@ ARG_POS, ARG_STAR, LITERAL_TYPE, MDEF, GDEF, CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr, is_final_node, -) + ARG_NAMED) from mypy import nodes from mypy.literals import literal, literal_hash from mypy.typeanal import has_any_from_unimported_type, check_for_explicit_any @@ -1647,6 +1647,22 @@ def visit_class_def(self, defn: ClassDef) -> None: with self.scope.push_class(defn.info): self.accept(defn.defs) self.binder = old_binder + for base in typ.mro[1:]: + if base.name() != 'object' and base.defn.info: + for method_name, method_symbol_node in base.defn.info.names.items(): + if method_name == '__init_subclass__': + name_expr = NameExpr(defn.name) + name_expr.node = typ + callee = MemberExpr(name_expr, '__init_subclass__') + args = list(defn.keywords.values()) + arg_names = list(defn.keywords.keys()) + arg_kinds = [ARG_NAMED] * len(args) + call_expr = CallExpr(callee, args, arg_kinds, arg_names) + call_expr.line = defn.line + call_expr.end_line = defn.end_line + self.expr_checker.accept(call_expr, allow_none_return=True, always_allow_any=True) + break + if not defn.has_incompatible_baseclass: # Otherwise we've already found errors; more errors are not useful self.check_multiple_inheritance(typ) From dd3df66ccd89658fb82287331b87b329e5734698 Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 12:33:44 -0400 Subject: [PATCH 02/30] added fixture --- .../fixtures/object_with_init_subclass.pyi | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test-data/unit/fixtures/object_with_init_subclass.pyi diff --git a/test-data/unit/fixtures/object_with_init_subclass.pyi b/test-data/unit/fixtures/object_with_init_subclass.pyi new file mode 100644 index 000000000000..c8a7803658fd --- /dev/null +++ b/test-data/unit/fixtures/object_with_init_subclass.pyi @@ -0,0 +1,53 @@ +from typing import Sequence, Iterator, TypeVar, Mapping, Iterable, Optional, Union, overload, Tuple, Generic + + +class object: + def __init__(self) -> None: pass + def __init_subclass__(cls, **kwargs) -> None: pass + +T = TypeVar('T') +KT = TypeVar('KT') +VT = TypeVar('VT') +# copy pased from primitives.pyi +class type: + def __init__(self, x) -> None: pass + +class int: + # Note: this is a simplification of the actual signature + def __init__(self, x: object = ..., base: int = ...) -> None: pass + def __add__(self, i: int) -> int: pass +class float: + def __float__(self) -> float: pass +class complex: pass +class bool(int): pass +class str(Sequence[str]): + def __add__(self, s: str) -> str: pass + def __iter__(self) -> Iterator[str]: pass + def __contains__(self, other: object) -> bool: pass + def __getitem__(self, item: int) -> str: pass + def format(self, *args) -> str: pass +class bytes(Sequence[int]): + def __iter__(self) -> Iterator[int]: pass + def __contains__(self, other: object) -> bool: pass + def __getitem__(self, item: int) -> int: pass +class bytearray: pass +class tuple(Generic[T]): pass +class function: pass +class ellipsis: pass + +# copy-pasted from dict.pyi +class dict(Mapping[KT, VT]): + @overload + def __init__(self, **kwargs: VT) -> None: pass + @overload + def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass + def __getitem__(self, key: KT) -> VT: pass + def __setitem__(self, k: KT, v: VT) -> None: pass + def __iter__(self) -> Iterator[KT]: pass + def __contains__(self, item: object) -> int: pass + def update(self, a: Mapping[KT, VT]) -> None: pass + @overload + def get(self, k: KT) -> Optional[VT]: pass + @overload + def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass + def __len__(self) -> int: ... From 1dfae8ce4fab04a19c6e4f540c37678ce4b83ec4 Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 12:34:51 -0400 Subject: [PATCH 03/30] update existing test; class B would fail at runtime --- test-data/unit/check-classes.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index c00ffd076a56..1f3bed994fda 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -379,7 +379,7 @@ class B(A): [case testOverride__init_subclass__WithDifferentSignature] class A: def __init_subclass__(cls, x: int) -> None: pass -class B(A): +class B(A): # E: Too few arguments for "__init_subclass__" of "A" def __init_subclass__(cls) -> None: pass [case testOverrideWithDecorator] From 081e19c963f508edee355e75aae428fe238ce3d9 Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 12:35:08 -0400 Subject: [PATCH 04/30] added tests --- test-data/unit/check-classes.test | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1f3bed994fda..3fc6303bbcae 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6142,3 +6142,45 @@ class C(B[int, T]): def __init__(self) -> None: # TODO: error message could be better. self.x: Tuple[str, T] # E: Incompatible types in assignment (expression has type "Tuple[str, T]", base class "A" defined the type as "Tuple[int, T]") + +[case testInitSubclassWrongType] +class Base: + default_name: str + + def __init_subclass__(cls, default_name: str): + super().__init_subclass__() + cls.default_name = default_name + return + + +class Child(Base, default_name=5): # E: Argument "default_name" to "__init_subclass__" of "Base" has incompatible type "int"; expected "str" + pass +[builtins fixtures/object_with_init_subclass.pyi] + +[case testInitSubclassTooFewArgs] +class Base: + default_name: str + + def __init_subclass__(cls, default_name: str, **kwargs): + super().__init_subclass__(**kwargs) + cls.default_name = default_name + return + +class Child(Base): # E: Too few arguments for "__init_subclass__" of "Base" + pass +[builtins fixtures/object_with_init_subclass.pyi] + +[case testInitSubclassOK] +class Base: + default_name: str + thing: int + + def __init_subclass__(cls, default_name: str, thing:int, **kwargs): + super().__init_subclass__(**kwargs) + cls.default_name = default_name + return + + +class Child(Base, thing=5, default_name=""): + pass +[builtins fixtures/object_with_init_subclass.pyi] \ No newline at end of file From 145d25468fd4e000a45da624ae56bd82a709f098 Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 13:01:17 -0400 Subject: [PATCH 05/30] base, not typ --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index ca3b2337bd7d..400a2dcdb00c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1652,7 +1652,7 @@ def visit_class_def(self, defn: ClassDef) -> None: for method_name, method_symbol_node in base.defn.info.names.items(): if method_name == '__init_subclass__': name_expr = NameExpr(defn.name) - name_expr.node = typ + name_expr.node = base callee = MemberExpr(name_expr, '__init_subclass__') args = list(defn.keywords.values()) arg_names = list(defn.keywords.keys()) From bd66bcace988166758f3b4f0adddd2facf65c621 Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 13:11:57 -0400 Subject: [PATCH 06/30] flake8 --- mypy/checker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 400a2dcdb00c..e063eb659382 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1660,7 +1660,9 @@ def visit_class_def(self, defn: ClassDef) -> None: call_expr = CallExpr(callee, args, arg_kinds, arg_names) call_expr.line = defn.line call_expr.end_line = defn.end_line - self.expr_checker.accept(call_expr, allow_none_return=True, always_allow_any=True) + self.expr_checker.accept(call_expr, + allow_none_return=True, + always_allow_any=True) break if not defn.has_incompatible_baseclass: From 1d12de78788a3fb5ae03ca27f260c7f201af2816 Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 13:12:39 -0400 Subject: [PATCH 07/30] trailing space --- test-data/unit/check-classes.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 3fc6303bbcae..635b15058d93 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6183,4 +6183,4 @@ class Base: class Child(Base, thing=5, default_name=""): pass -[builtins fixtures/object_with_init_subclass.pyi] \ No newline at end of file +[builtins fixtures/object_with_init_subclass.pyi] From 7dccdb7e65a2a78d09c5a973d10ffd7880e4be3e Mon Sep 17 00:00:00 2001 From: denis Date: Tue, 3 Sep 2019 13:21:40 -0400 Subject: [PATCH 08/30] fix type --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index e063eb659382..96d256c6cd64 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1655,7 +1655,7 @@ def visit_class_def(self, defn: ClassDef) -> None: name_expr.node = base callee = MemberExpr(name_expr, '__init_subclass__') args = list(defn.keywords.values()) - arg_names = list(defn.keywords.keys()) + arg_names = list(defn.keywords.keys()) # type: List[Optional[str]] arg_kinds = [ARG_NAMED] * len(args) call_expr = CallExpr(callee, args, arg_kinds, arg_names) call_expr.line = defn.line From f5cc79a6dea0e2713b5f94387d7702c3fbfed8e4 Mon Sep 17 00:00:00 2001 From: denis Date: Wed, 4 Sep 2019 13:19:16 -0400 Subject: [PATCH 09/30] added test for metaclass --- test-data/unit/check-classes.test | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 635b15058d93..0e1b7f1f7476 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6184,3 +6184,14 @@ class Base: class Child(Base, thing=5, default_name=""): pass [builtins fixtures/object_with_init_subclass.pyi] + +[case testInitSubclassWithMetaclassOK] +class Base(type): + thing: int + + def __init_subclass__(cls, thing: int): + cls.thing = thing + + +class Child(Base, metaclass=Base, thing=0): + pass \ No newline at end of file From 1c62e37db43ae0e367c5130d4f9401df358d29f5 Mon Sep 17 00:00:00 2001 From: denis Date: Wed, 4 Sep 2019 13:50:38 -0400 Subject: [PATCH 10/30] make signature conform to typeshed --- test-data/unit/check-classes.test | 4 ++-- test-data/unit/fixtures/object_with_init_subclass.pyi | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 0e1b7f1f7476..16eae338c2ae 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6162,7 +6162,7 @@ class Base: default_name: str def __init_subclass__(cls, default_name: str, **kwargs): - super().__init_subclass__(**kwargs) + super().__init_subclass__() cls.default_name = default_name return @@ -6176,7 +6176,7 @@ class Base: thing: int def __init_subclass__(cls, default_name: str, thing:int, **kwargs): - super().__init_subclass__(**kwargs) + super().__init_subclass__() cls.default_name = default_name return diff --git a/test-data/unit/fixtures/object_with_init_subclass.pyi b/test-data/unit/fixtures/object_with_init_subclass.pyi index c8a7803658fd..407e387bc8c0 100644 --- a/test-data/unit/fixtures/object_with_init_subclass.pyi +++ b/test-data/unit/fixtures/object_with_init_subclass.pyi @@ -1,9 +1,8 @@ from typing import Sequence, Iterator, TypeVar, Mapping, Iterable, Optional, Union, overload, Tuple, Generic - class object: - def __init__(self) -> None: pass - def __init_subclass__(cls, **kwargs) -> None: pass + def __init__(self) -> None: ... + def __init_subclass__(cls) -> None: ... T = TypeVar('T') KT = TypeVar('KT') From 6729fe83126695a16c1a4104aa53377741c4b407 Mon Sep 17 00:00:00 2001 From: denis Date: Wed, 4 Sep 2019 14:01:26 -0400 Subject: [PATCH 11/30] refactor into a method + check for metaclass + added comments --- mypy/checker.py | 69 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 96d256c6cd64..fe09c53e2746 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1647,24 +1647,7 @@ def visit_class_def(self, defn: ClassDef) -> None: with self.scope.push_class(defn.info): self.accept(defn.defs) self.binder = old_binder - for base in typ.mro[1:]: - if base.name() != 'object' and base.defn.info: - for method_name, method_symbol_node in base.defn.info.names.items(): - if method_name == '__init_subclass__': - name_expr = NameExpr(defn.name) - name_expr.node = base - callee = MemberExpr(name_expr, '__init_subclass__') - args = list(defn.keywords.values()) - arg_names = list(defn.keywords.keys()) # type: List[Optional[str]] - arg_kinds = [ARG_NAMED] * len(args) - call_expr = CallExpr(callee, args, arg_kinds, arg_names) - call_expr.line = defn.line - call_expr.end_line = defn.end_line - self.expr_checker.accept(call_expr, - allow_none_return=True, - always_allow_any=True) - break - + self.check_init_subclass(defn) if not defn.has_incompatible_baseclass: # Otherwise we've already found errors; more errors are not useful self.check_multiple_inheritance(typ) @@ -1694,6 +1677,56 @@ def visit_class_def(self, defn: ClassDef) -> None: if typ.is_protocol and typ.defn.type_vars: self.check_protocol_variance(defn) + def check_init_subclass(self, defn: ClassDef): + """ + Check that the number of args to __init_subclass__, as well as typing are both correct + In this example: + + 1 class Base: + 2 def __init_subclass__(cls, thing: int): + 3 pass + 4 class Child(Base, thing=5): + 5 def __init_subclass__(cls): + 6 pass + 7 Child() + + Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here + Child.__init_subclass__ is never called + """ + typ = defn.info + # at runtime, only Base.__init_subclass__ will be called + # we skip the current class itself + for base in typ.mro[1:]: + # 'object.__init_subclass__ is a dummy method with no arguments, always defined + # there is no use to call it + if base.name() != 'object' \ + and base.defn.info: # there are "NOT_READY" instances + # during the tests, so I filter them out... + for method_name, method_symbol_node in base.defn.info.names.items(): + if method_name == '__init_subclass__': + name_expr = NameExpr(defn.name) + name_expr.node = base + callee = MemberExpr(name_expr, '__init_subclass__') + args = list(defn.keywords.values()) + arg_names = list(defn.keywords.keys()) # type: List[Optional[str]] + # 'metaclass' keyword is consumed by the rest of the type machinery, + # and is never passed to __init_subclass__ implementations + if 'metaclass' in arg_names: + idx = arg_names.index('metaclass') + arg_names.pop(idx) + args.pop(idx) + arg_kinds = [ARG_NAMED] * len(args) + call_expr = CallExpr(callee, args, arg_kinds, arg_names) + call_expr.line = defn.line + call_expr.column = defn.column + call_expr.end_line = defn.end_line + self.expr_checker.accept(call_expr, + allow_none_return=True, + always_allow_any=True) + # we are only interested in the first Base having __init_subclass__ + # all other (highest) bases have already been checked + break + def check_protocol_variance(self, defn: ClassDef) -> None: """Check that protocol definition is compatible with declared variances of type variables. From 502aa3fcef070d0229f3e147ae8d2c11861c302c Mon Sep 17 00:00:00 2001 From: denis Date: Wed, 4 Sep 2019 14:04:51 -0400 Subject: [PATCH 12/30] space --- test-data/unit/check-classes.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 16eae338c2ae..7450bf0c7d67 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6194,4 +6194,4 @@ class Base(type): class Child(Base, metaclass=Base, thing=0): - pass \ No newline at end of file + pass From dfb6551e7f32aebe173e604aef3629e167e0283f Mon Sep 17 00:00:00 2001 From: denis Date: Wed, 4 Sep 2019 14:35:01 -0400 Subject: [PATCH 13/30] missing return + annotation --- mypy/checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index fe09c53e2746..9a8a31529057 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1677,7 +1677,7 @@ def visit_class_def(self, defn: ClassDef) -> None: if typ.is_protocol and typ.defn.type_vars: self.check_protocol_variance(defn) - def check_init_subclass(self, defn: ClassDef): + def check_init_subclass(self, defn: ClassDef) -> None: """ Check that the number of args to __init_subclass__, as well as typing are both correct In this example: @@ -1726,6 +1726,7 @@ def check_init_subclass(self, defn: ClassDef): # we are only interested in the first Base having __init_subclass__ # all other (highest) bases have already been checked break + return def check_protocol_variance(self, defn: ClassDef) -> None: """Check that protocol definition is compatible with declared From b860c3a41e993ddeaaee55ec4f8bf8ce5189d0d2 Mon Sep 17 00:00:00 2001 From: "g.denis" Date: Thu, 5 Sep 2019 10:24:06 -0400 Subject: [PATCH 14/30] Update mypy/checker.py Co-Authored-By: Ivan Levkivskyi --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9a8a31529057..c9468bc810bc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1724,7 +1724,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: allow_none_return=True, always_allow_any=True) # we are only interested in the first Base having __init_subclass__ - # all other (highest) bases have already been checked + # all other (highest) bases have already been checked. break return From 012b81764891b428bfdea3a065174ae0dc0a6056 Mon Sep 17 00:00:00 2001 From: "g.denis" Date: Fri, 6 Sep 2019 13:51:27 -0400 Subject: [PATCH 15/30] Update mypy/checker.py Co-Authored-By: Ivan Levkivskyi --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index c9468bc810bc..9cd85adf4ebc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1690,7 +1690,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: 6 pass 7 Child() - Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here + Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here. Child.__init_subclass__ is never called """ typ = defn.info From a8fcb3eebcaf11499a89f2128e12deba8b9d8469 Mon Sep 17 00:00:00 2001 From: "g.denis" Date: Fri, 6 Sep 2019 13:51:51 -0400 Subject: [PATCH 16/30] Update mypy/checker.py Co-Authored-By: Ivan Levkivskyi --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9cd85adf4ebc..9a5843f3a599 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1723,7 +1723,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: self.expr_checker.accept(call_expr, allow_none_return=True, always_allow_any=True) - # we are only interested in the first Base having __init_subclass__ + # We are only interested in the first Base having __init_subclass__ # all other (highest) bases have already been checked. break return From ef295b58eebbb1a508a0b1eedde57cf16bb1f823 Mon Sep 17 00:00:00 2001 From: "g.denis" Date: Fri, 6 Sep 2019 13:51:58 -0400 Subject: [PATCH 17/30] Update mypy/checker.py Co-Authored-By: Ivan Levkivskyi --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9a5843f3a599..44fc1c803070 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1694,7 +1694,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: Child.__init_subclass__ is never called """ typ = defn.info - # at runtime, only Base.__init_subclass__ will be called + # At runtime, only Base.__init_subclass__ will be called # we skip the current class itself for base in typ.mro[1:]: # 'object.__init_subclass__ is a dummy method with no arguments, always defined From ddc1dce84fd0f0eca5ea6502387e6a9ed86b1f60 Mon Sep 17 00:00:00 2001 From: "g.denis" Date: Fri, 6 Sep 2019 13:52:05 -0400 Subject: [PATCH 18/30] Update mypy/checker.py Co-Authored-By: Ivan Levkivskyi --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 44fc1c803070..acc282b352b4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1695,7 +1695,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: """ typ = defn.info # At runtime, only Base.__init_subclass__ will be called - # we skip the current class itself + # we skip the current class itself. for base in typ.mro[1:]: # 'object.__init_subclass__ is a dummy method with no arguments, always defined # there is no use to call it From 2c0c20f7d2ae26c0eb028b3e9a57271ca6fb08da Mon Sep 17 00:00:00 2001 From: denis Date: Fri, 6 Sep 2019 16:39:36 -0400 Subject: [PATCH 19/30] added test with imports --- test-data/unit/check-classes.test | 39 ++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7450bf0c7d67..5c306701e260 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6152,7 +6152,6 @@ class Base: cls.default_name = default_name return - class Child(Base, default_name=5): # E: Argument "default_name" to "__init_subclass__" of "Base" has incompatible type "int"; expected "str" pass [builtins fixtures/object_with_init_subclass.pyi] @@ -6170,6 +6169,20 @@ class Child(Base): # E: Too few arguments for "__init_subclass__" of "Base" pass [builtins fixtures/object_with_init_subclass.pyi] +[case testInitSubclassTooFewArgs2] +class Base: + default_name: str + + def __init_subclass__(cls, default_name: str, thing: int): + super().__init_subclass__() + cls.default_name = default_name + return +# TODO implement this, so that no error is raised? +d = {"default_name": "abc", "thing": 0} +class Child(Base, **d): # E: Too few arguments for "__init_subclass__" of "Base" + pass +[builtins fixtures/object_with_init_subclass.pyi] + [case testInitSubclassOK] class Base: default_name: str @@ -6180,7 +6193,6 @@ class Base: cls.default_name = default_name return - class Child(Base, thing=5, default_name=""): pass [builtins fixtures/object_with_init_subclass.pyi] @@ -6192,6 +6204,27 @@ class Base(type): def __init_subclass__(cls, thing: int): cls.thing = thing - class Child(Base, metaclass=Base, thing=0): pass + +[case testTooManyArgsForObject] +class A(thing=5): + pass +[out] +main:1: error: Unexpected keyword argument "thing" for "__init_subclass__" of "object" +tmp/builtins.pyi:5: note: "__init_subclass__" of "object" defined here +[builtins fixtures/object_with_init_subclass.pyi] + +[case testInitSubclassWithImports] +from init_subclass.a import Base +class Child(Base, thing=5): # E: Missing positional arguments "default_name", "kwargs" in call to "__init_subclass__" of "Base" + pass +[file init_subclass/a.py] +class Base: + default_name: str + thing: int + + def __init_subclass__(cls, default_name: str, thing:int, **kwargs): + pass +[file init_subclass/__init__.py] +[builtins fixtures/object_with_init_subclass.pyi] From 67a761b454be3a5ad6db0f859fc415cc5d42e609 Mon Sep 17 00:00:00 2001 From: denis Date: Fri, 6 Sep 2019 16:41:19 -0400 Subject: [PATCH 20/30] check for "object" too --- mypy/checker.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index acc282b352b4..8f2f6ab5ddb8 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1696,12 +1696,11 @@ def check_init_subclass(self, defn: ClassDef) -> None: typ = defn.info # At runtime, only Base.__init_subclass__ will be called # we skip the current class itself. + found = False for base in typ.mro[1:]: - # 'object.__init_subclass__ is a dummy method with no arguments, always defined - # there is no use to call it - if base.name() != 'object' \ - and base.defn.info: # there are "NOT_READY" instances - # during the tests, so I filter them out... + # there are "NOT_READY" instances + # during the tests, so I filter them out... + if base.defn.info: for method_name, method_symbol_node in base.defn.info.names.items(): if method_name == '__init_subclass__': name_expr = NameExpr(defn.name) @@ -1723,9 +1722,13 @@ def check_init_subclass(self, defn: ClassDef) -> None: self.expr_checker.accept(call_expr, allow_none_return=True, always_allow_any=True) - # We are only interested in the first Base having __init_subclass__ - # all other (highest) bases have already been checked. + # there is only one such method method + found = True break + # We are only interested in the first Base having __init_subclass__ + # all other (highest) bases have already been checked. + if found: + break return def check_protocol_variance(self, defn: ClassDef) -> None: From 79208fd700c581e66dc72f4fee70c494f8781927 Mon Sep 17 00:00:00 2001 From: denis Date: Fri, 6 Sep 2019 16:41:37 -0400 Subject: [PATCH 21/30] stylistic changes --- mypy/checker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8f2f6ab5ddb8..d4b4717b00cd 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1679,9 +1679,9 @@ def visit_class_def(self, defn: ClassDef) -> None: def check_init_subclass(self, defn: ClassDef) -> None: """ - Check that the number of args to __init_subclass__, as well as typing are both correct - In this example: + Check that keywords in a class definition are valid arguments for __init_subclass__(). + In this example: 1 class Base: 2 def __init_subclass__(cls, thing: int): 3 pass @@ -1691,7 +1691,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: 7 Child() Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here. - Child.__init_subclass__ is never called + Child.__init_subclass__ is never called. """ typ = defn.info # At runtime, only Base.__init_subclass__ will be called From 8eb7b8d53bd4a336a709b02379a904c89fe9dd63 Mon Sep 17 00:00:00 2001 From: denis Date: Fri, 6 Sep 2019 16:49:05 -0400 Subject: [PATCH 22/30] added test --- test-data/unit/check-classes.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 5c306701e260..93d2ab6edece 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6228,3 +6228,20 @@ class Base: pass [file init_subclass/__init__.py] [builtins fixtures/object_with_init_subclass.pyi] + +[case testInitSubclassWithNonImportedBaseDoesntCrash] +class Child(Base, thing=5): + pass +[file init_subclass/a.py] +class Base: + default_name: str + thing: int + + def __init_subclass__(cls, default_name: str, thing:int, **kwargs): + pass +[file init_subclass/__init__.py] +[builtins fixtures/object_with_init_subclass.pyi] +[out] +main:1: error: Unexpected keyword argument "thing" for "__init_subclass__" of "object" +main:1: error: Name 'Base' is not defined +tmp/builtins.pyi:5: note: "__init_subclass__" of "object" defined here From 9a7ab315427673ae23b2de675607d0127429f6de Mon Sep 17 00:00:00 2001 From: Denis Gantsev Date: Sun, 15 Sep 2019 22:03:20 +0200 Subject: [PATCH 23/30] simplify logic --- mypy/checker.py | 54 +++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d4b4717b00cd..9b6640156153 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1696,39 +1696,31 @@ def check_init_subclass(self, defn: ClassDef) -> None: typ = defn.info # At runtime, only Base.__init_subclass__ will be called # we skip the current class itself. - found = False - for base in typ.mro[1:]: - # there are "NOT_READY" instances - # during the tests, so I filter them out... - if base.defn.info: - for method_name, method_symbol_node in base.defn.info.names.items(): - if method_name == '__init_subclass__': - name_expr = NameExpr(defn.name) - name_expr.node = base - callee = MemberExpr(name_expr, '__init_subclass__') - args = list(defn.keywords.values()) - arg_names = list(defn.keywords.keys()) # type: List[Optional[str]] - # 'metaclass' keyword is consumed by the rest of the type machinery, - # and is never passed to __init_subclass__ implementations - if 'metaclass' in arg_names: - idx = arg_names.index('metaclass') - arg_names.pop(idx) - args.pop(idx) - arg_kinds = [ARG_NAMED] * len(args) - call_expr = CallExpr(callee, args, arg_kinds, arg_names) - call_expr.line = defn.line - call_expr.column = defn.column - call_expr.end_line = defn.end_line - self.expr_checker.accept(call_expr, - allow_none_return=True, - always_allow_any=True) - # there is only one such method method - found = True - break + for base in defn.info.mro[1:]: + if not '__init_subclass__' in base.names: + continue + name_expr = NameExpr(defn.name) + name_expr.node = base + callee = MemberExpr(name_expr, '__init_subclass__') + args = list(defn.keywords.values()) + arg_names = list(defn.keywords.keys()) # type: List[Optional[str]] + # 'metaclass' keyword is consumed by the rest of the type machinery, + # and is never passed to __init_subclass__ implementations + if 'metaclass' in arg_names: + idx = arg_names.index('metaclass') + arg_names.pop(idx) + args.pop(idx) + arg_kinds = [ARG_NAMED] * len(args) + call_expr = CallExpr(callee, args, arg_kinds, arg_names) + call_expr.line = defn.line + call_expr.column = defn.column + call_expr.end_line = defn.end_line + self.expr_checker.accept(call_expr, + allow_none_return=True, + always_allow_any=True) # We are only interested in the first Base having __init_subclass__ # all other (highest) bases have already been checked. - if found: - break + break return def check_protocol_variance(self, defn: ClassDef) -> None: From 2281a8679c1dcdbb7b6d6b400b1f71cf32ad9eff Mon Sep 17 00:00:00 2001 From: Denis Gantsev Date: Sun, 15 Sep 2019 22:04:07 +0200 Subject: [PATCH 24/30] modify test --- test-data/unit/check-classes.test | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 93d2ab6edece..0cf96e6fb35e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6229,19 +6229,12 @@ class Base: [file init_subclass/__init__.py] [builtins fixtures/object_with_init_subclass.pyi] -[case testInitSubclassWithNonImportedBaseDoesntCrash] -class Child(Base, thing=5): - pass +[case testInitSubclassWithImportsOK] +from init_subclass.a import MidBase +class Main(MidBase, test=True): pass [file init_subclass/a.py] class Base: - default_name: str - thing: int - - def __init_subclass__(cls, default_name: str, thing:int, **kwargs): - pass + def __init_subclass__(cls, **kwargs) -> None: pass +class MidBase(Base): pass [file init_subclass/__init__.py] [builtins fixtures/object_with_init_subclass.pyi] -[out] -main:1: error: Unexpected keyword argument "thing" for "__init_subclass__" of "object" -main:1: error: Name 'Base' is not defined -tmp/builtins.pyi:5: note: "__init_subclass__" of "object" defined here From f8f330af4175c5012fb4979cd4b3d823510bd15c Mon Sep 17 00:00:00 2001 From: Denis Gantsev Date: Sun, 15 Sep 2019 23:08:10 +0200 Subject: [PATCH 25/30] sync typeshed to master --- mypy/typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeshed b/mypy/typeshed index b2947821832b..2edb36e9935e 160000 --- a/mypy/typeshed +++ b/mypy/typeshed @@ -1 +1 @@ -Subproject commit b2947821832bb4d098985383c75c2d712b12f133 +Subproject commit 2edb36e9935ee725d5424b0fc5984e7421d7fe40 From 0850dff23f81b5ff8fa3c914dd615850a829157a Mon Sep 17 00:00:00 2001 From: Denis Gantsev Date: Sun, 15 Sep 2019 23:29:34 +0200 Subject: [PATCH 26/30] flake8 --- mypy/checker.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 25209482fbb8..4bc0b6d0392d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1716,11 +1716,10 @@ def check_init_subclass(self, defn: ClassDef) -> None: Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here. Child.__init_subclass__ is never called. """ - typ = defn.info # At runtime, only Base.__init_subclass__ will be called # we skip the current class itself. for base in defn.info.mro[1:]: - if not '__init_subclass__' in base.names: + if '__init_subclass__' not in base.names: continue name_expr = NameExpr(defn.name) name_expr.node = base From 798ff33d916de3de3c9415383461c5bcda544ffb Mon Sep 17 00:00:00 2001 From: "g.denis" Date: Sat, 21 Sep 2019 14:00:01 +0200 Subject: [PATCH 27/30] Update mypy/checker.py Co-Authored-By: Ivan Levkivskyi --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4bc0b6d0392d..2f0f0db77fda 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1716,7 +1716,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here. Child.__init_subclass__ is never called. """ - # At runtime, only Base.__init_subclass__ will be called + # At runtime, only Base.__init_subclass__ will be called, so # we skip the current class itself. for base in defn.info.mro[1:]: if '__init_subclass__' not in base.names: From 6f5bda98149d8a74882b5567e3683f06af1f148c Mon Sep 17 00:00:00 2001 From: "g.denis" Date: Sat, 21 Sep 2019 14:00:14 +0200 Subject: [PATCH 28/30] Update mypy/checker.py Co-Authored-By: Ivan Levkivskyi --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2f0f0db77fda..ee456bba1b56 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1741,7 +1741,7 @@ def check_init_subclass(self, defn: ClassDef) -> None: allow_none_return=True, always_allow_any=True) # We are only interested in the first Base having __init_subclass__ - # all other (highest) bases have already been checked. + # all other bases have already been checked. break return From ab8a6c8f542b6fbf333ab3804cc57aa0a8f8e80a Mon Sep 17 00:00:00 2001 From: Denis Gantsev Date: Sat, 21 Sep 2019 14:09:23 +0200 Subject: [PATCH 29/30] move comment line --- mypy/checker.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index ee456bba1b56..a3fd226e1d21 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1701,8 +1701,7 @@ def visit_class_def(self, defn: ClassDef) -> None: self.check_protocol_variance(defn) def check_init_subclass(self, defn: ClassDef) -> None: - """ - Check that keywords in a class definition are valid arguments for __init_subclass__(). + """Check that keywords in a class definition are valid arguments for __init_subclass__(). In this example: 1 class Base: From 7da5d11f2a502d65db13ace407091aed511acb8b Mon Sep 17 00:00:00 2001 From: Denis Gantsev Date: Sat, 21 Sep 2019 14:32:41 +0200 Subject: [PATCH 30/30] undo pin move --- mypy/typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeshed b/mypy/typeshed index 2edb36e9935e..3fc8aec42555 160000 --- a/mypy/typeshed +++ b/mypy/typeshed @@ -1 +1 @@ -Subproject commit 2edb36e9935ee725d5424b0fc5984e7421d7fe40 +Subproject commit 3fc8aec4255514436545b80245c13faf10c432a1 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy