From ba7e3099360d61b6bb2615e2cdc458632aa5a79c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 9 Nov 2023 19:17:53 +0300 Subject: [PATCH 1/2] Do not allow `TypedDict` classes with `metaclass=` --- mypy/semanal.py | 2 ++ test-data/unit/check-typeddict.test | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 6f322af816ea3..9787c282a600a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1745,6 +1745,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: if info is None: self.mark_incomplete(defn.name, defn) else: + if defn.keywords and "metaclass" in defn.keywords: + self.fail('"TypedDict" cannot have a metaclass', defn.keywords["metaclass"]) self.prepare_class_def(defn, info, custom_names=True) return True return False diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index c1c791304a15a..ea81643ad8148 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -3396,3 +3396,29 @@ reveal_type(b["a"]) # N: Revealed type is "Union[builtins.str, None]" reveal_type(b["g"]) # N: Revealed type is "Union[builtins.int, None]" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] + +[case testTypedDictWithMetaclass] +from typing import TypedDict, Generic, TypeVar + +T = TypeVar('T') + +class Meta(type): ... + +class WithMetaKeyword(TypedDict, metaclass=Meta): # E: "TypedDict" cannot have a metaclass + ... + +class GenericWithMetaKeyword(TypedDict, Generic[T], metaclass=Meta): # E: "TypedDict" cannot have a metaclass + ... + +class PositionOfErrorIsPrecise( + TypedDict, + metaclass=Meta, # E: "TypedDict" cannot have a metaclass +): + ... + +# We still don't allow this, because the implementation is much easier +# and it does not make any practical sense to do it: +class WithTypeMeta(TypedDict, metaclass=type): # E: "TypedDict" cannot have a metaclass + ... +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] From aafc409f87eeb819d387b8eda141ccec44a5f1f0 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 10 Nov 2023 13:50:42 +0300 Subject: [PATCH 2/2] Do not allow other keywords as well --- mypy/semanal.py | 2 -- mypy/semanal_typeddict.py | 6 ++++++ test-data/unit/check-typeddict.test | 15 ++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 9787c282a600a..6f322af816ea3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1745,8 +1745,6 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: if info is None: self.mark_incomplete(defn.name, defn) else: - if defn.keywords and "metaclass" in defn.keywords: - self.fail('"TypedDict" cannot have a metaclass', defn.keywords["metaclass"]) self.prepare_class_def(defn, info, custom_names=True) return True return False diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 5104d31f5c266..914d8a1fa530a 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -323,6 +323,12 @@ def analyze_typeddict_classdef_fields( total: bool | None = True if "total" in defn.keywords: total = require_bool_literal_argument(self.api, defn.keywords["total"], "total", True) + if defn.keywords and defn.keywords.keys() != {"total"}: + msg = ( + '"TypedDict" class definition cannot have keywords ' + 'other than "total", got: "{}"' + ) + self.fail(msg.format(", ".join(defn.keywords)), defn) required_keys = { field for (field, t) in zip(fields, types) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index ea81643ad8148..20d9cfff411ea 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -3404,21 +3404,18 @@ T = TypeVar('T') class Meta(type): ... -class WithMetaKeyword(TypedDict, metaclass=Meta): # E: "TypedDict" cannot have a metaclass +class WithMetaKeyword(TypedDict, metaclass=Meta): # E: "TypedDict" class definition cannot have keywords other than "total", got: "metaclass" ... -class GenericWithMetaKeyword(TypedDict, Generic[T], metaclass=Meta): # E: "TypedDict" cannot have a metaclass - ... - -class PositionOfErrorIsPrecise( - TypedDict, - metaclass=Meta, # E: "TypedDict" cannot have a metaclass -): +class GenericWithMetaKeyword(TypedDict, Generic[T], metaclass=Meta): # E: "TypedDict" class definition cannot have keywords other than "total", got: "metaclass" ... # We still don't allow this, because the implementation is much easier # and it does not make any practical sense to do it: -class WithTypeMeta(TypedDict, metaclass=type): # E: "TypedDict" cannot have a metaclass +class WithTypeMeta(TypedDict, metaclass=type): # E: "TypedDict" class definition cannot have keywords other than "total", got: "metaclass" + ... + +class OtherKeywords(TypedDict, a=1, b=2, c=3, total=True): # E: "TypedDict" class definition cannot have keywords other than "total", got: "a, b, c, total" ... [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi]