From 9b4165581e7f5849773d7889e6fe83f49c241dfc Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Sep 2022 12:45:55 +0300 Subject: [PATCH 1/4] Treat `Any` metaclass the same as `Any` base class, refs #13599 --- mypy/semanal.py | 12 ++++++------ test-data/unit/check-classes.test | 21 ++++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 7e428f06e9ba..5fa4e49afd7c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2074,12 +2074,12 @@ def get_declared_metaclass( # Probably a name error - it is already handled elsewhere return None, False if isinstance(sym.node, Var) and isinstance(get_proper_type(sym.node.type), AnyType): - # 'Any' metaclass -- just ignore it. - # - # TODO: A better approach would be to record this information - # and assume that the type object supports arbitrary - # attributes, similar to an 'Any' base class. - return None, False + # Create a fake TypeInfo that fallbacks to `Any`, basically allowing + # all the attributes. Same thing as we do for `Any` base class. + any_info = self.make_empty_type_info(ClassDef(sym.node.name, Block([]))) + any_info.fallback_to_any = True + any_info._fullname = sym.node.fullname + return Instance(any_info, []), False if isinstance(sym.node, PlaceholderNode): return None, True # defer later in the caller diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 9bf2bbd839ed..43325aa4efbf 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4392,6 +4392,17 @@ def f(TB: Type[B]): reveal_type(TB) # N: Revealed type is "Type[__main__.B]" reveal_type(TB.x) # N: Revealed type is "builtins.int" +[case testMetaclassAsAny] +from typing import Any, ClassVar + +MyAny: Any +class WithMeta(metaclass=MyAny): + x: ClassVar[int] + +reveal_type(WithMeta.a) # N: Revealed type is "Any" +reveal_type(WithMeta.m) # N: Revealed type is "Any" +reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" + [case testMetaclassIterable] from typing import Iterable, Iterator @@ -4476,15 +4487,7 @@ from missing import M class A(metaclass=M): y = 0 reveal_type(A.y) # N: Revealed type is "builtins.int" -A.x # E: "Type[A]" has no attribute "x" - -[case testAnyMetaclass] -from typing import Any -M = None # type: Any -class A(metaclass=M): - y = 0 -reveal_type(A.y) # N: Revealed type is "builtins.int" -A.x # E: "Type[A]" has no attribute "x" +reveal_type(A.x) # N: Revealed type is "Any" [case testValidTypeAliasAsMetaclass] from typing_extensions import TypeAlias From ec23452707c7e71a762e3434dbed33cd1aec5287 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Sep 2022 13:07:18 +0300 Subject: [PATCH 2/4] Handle `disallow_subclassing_any` --- mypy/semanal.py | 5 +++++ test-data/unit/check-classes.test | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5fa4e49afd7c..761e88c27769 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2079,6 +2079,11 @@ def get_declared_metaclass( any_info = self.make_empty_type_info(ClassDef(sym.node.name, Block([]))) any_info.fallback_to_any = True any_info._fullname = sym.node.fullname + if self.options.disallow_subclassing_any: + self.fail( + f'Class cannot use "{any_info.fullname}" as a metaclass (has type "Any")', + metaclass_expr, + ) return Instance(any_info, []), False if isinstance(sym.node, PlaceholderNode): return None, True # defer later in the caller diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 43325aa4efbf..4ecdbe156e62 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4403,6 +4403,21 @@ reveal_type(WithMeta.a) # N: Revealed type is "Any" reveal_type(WithMeta.m) # N: Revealed type is "Any" reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" +[case testMetaclassAsAnyWithAFlag] +# flags: --config-file tmp/mypy.ini +from typing import Any, ClassVar + +MyAny: Any +class WithMeta(metaclass=MyAny): # E: Class cannot use "__main__.MyAny" as a metaclass (has type "Any") + x: ClassVar[int] + +reveal_type(WithMeta.a) # N: Revealed type is "Any" +reveal_type(WithMeta.m) # N: Revealed type is "Any" +reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" +[file mypy.ini] +\[mypy] +disallow_subclassing_any = True + [case testMetaclassIterable] from typing import Iterable, Iterator From 7a0c1df08db4aa7c65ce50e116b3782ad46eadb8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Sep 2022 13:21:46 +0300 Subject: [PATCH 3/4] Test instance prop access --- test-data/unit/check-classes.test | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 4ecdbe156e62..bf63a03f553a 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4399,9 +4399,12 @@ MyAny: Any class WithMeta(metaclass=MyAny): x: ClassVar[int] -reveal_type(WithMeta.a) # N: Revealed type is "Any" -reveal_type(WithMeta.m) # N: Revealed type is "Any" -reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" +reveal_type(WithMeta.a) # N: Revealed type is "Any" +reveal_type(WithMeta.m) # N: Revealed type is "Any" +reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" +reveal_type(WithMeta().x) # N: Revealed type is "builtins.int" +WithMeta().m # E: "WithMeta" has no attribute "m" +WithMeta().a # E: "WithMeta" has no attribute "a" [case testMetaclassAsAnyWithAFlag] # flags: --config-file tmp/mypy.ini @@ -4411,9 +4414,12 @@ MyAny: Any class WithMeta(metaclass=MyAny): # E: Class cannot use "__main__.MyAny" as a metaclass (has type "Any") x: ClassVar[int] -reveal_type(WithMeta.a) # N: Revealed type is "Any" -reveal_type(WithMeta.m) # N: Revealed type is "Any" -reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" +reveal_type(WithMeta.a) # N: Revealed type is "Any" +reveal_type(WithMeta.m) # N: Revealed type is "Any" +reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" +reveal_type(WithMeta().x) # N: Revealed type is "builtins.int" +WithMeta().m # E: "WithMeta" has no attribute "m" +WithMeta().a # E: "WithMeta" has no attribute "a" [file mypy.ini] \[mypy] disallow_subclassing_any = True From 1d35b220e3d07efd5ef631a8d10da9c91598734d Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 4 Sep 2022 14:22:55 +0300 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Alex Waygood --- test-data/unit/check-classes.test | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index bf63a03f553a..1f7b60145de5 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4407,7 +4407,7 @@ WithMeta().m # E: "WithMeta" has no attribute "m" WithMeta().a # E: "WithMeta" has no attribute "a" [case testMetaclassAsAnyWithAFlag] -# flags: --config-file tmp/mypy.ini +# flags: --disallow-subclassing-any from typing import Any, ClassVar MyAny: Any @@ -4420,9 +4420,6 @@ reveal_type(WithMeta.x) # N: Revealed type is "builtins.int" reveal_type(WithMeta().x) # N: Revealed type is "builtins.int" WithMeta().m # E: "WithMeta" has no attribute "m" WithMeta().a # E: "WithMeta" has no attribute "a" -[file mypy.ini] -\[mypy] -disallow_subclassing_any = True [case testMetaclassIterable] from typing import Iterable, Iterator