From fe6fca57dd85b25d1e969d7cfe736a61cc62cfbb Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Mon, 8 Nov 2021 03:41:28 +0800 Subject: [PATCH 1/3] Fix --- mypy/semanal_typeddict.py | 19 +++++++++++++------ test-data/unit/check-typeddict.test | 12 ++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index f70bbe427124f..418c7f597196e 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -67,16 +67,23 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ defn.analyzed.line = defn.line defn.analyzed.column = defn.column return True, info + # Extending/merging existing TypedDicts - if any(not isinstance(expr, RefExpr) or - expr.fullname not in TPDICT_NAMES and - not self.is_typeddict(expr) for expr in defn.base_type_exprs): - self.fail("All bases of a new TypedDict must be TypedDict types", defn) - typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) + typeddict_bases = [] + typeddict_bases_set = set() + for expr in defn.base_type_exprs: + if (not isinstance(expr, RefExpr) or + expr.fullname not in TPDICT_NAMES and not self.is_typeddict(expr)): + self.fail("All bases of a new TypedDict must be TypedDict types", defn) + elif self.is_typeddict(expr): + if expr.fullname not in typeddict_bases_set: + typeddict_bases_set.add(expr.fullname) + typeddict_bases.append(expr) + else: + self.fail('Duplicate base class "%s"' % expr.name, defn) keys: List[str] = [] types = [] required_keys = set() - # Iterate over bases in reverse order so that leftmost base class' keys take precedence for base in reversed(typeddict_bases): assert isinstance(base, RefExpr) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 04bbc9b2b4d4b..eae26f6c98c00 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -171,6 +171,18 @@ p: Point2D reveal_type(p) # N: Revealed type is "TypedDict('__main__.Point2D', {'x': builtins.int, 'y': builtins.int})" [builtins fixtures/dict.pyi] +[case testCannotCreateTypedDictWithDuplicateBases] +# https://github.com/python/mypy/issues/3673 +from typing import TypedDict + +class A(TypedDict): + x: str + y: int + +class B(A, A): # E: Duplicate base class "A" + z: str +[typing fixtures/typing-typeddict.pyi] + [case testCannotCreateTypedDictWithClassWithOtherStuff] # flags: --python-version 3.6 from mypy_extensions import TypedDict From 6ef133709fb67179567def9f9cd97cb005ed56ea Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Mon, 8 Nov 2021 04:10:22 +0800 Subject: [PATCH 2/3] Fix type check --- mypy/semanal_typeddict.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 418c7f597196e..46565e98cec70 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -80,7 +80,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ typeddict_bases_set.add(expr.fullname) typeddict_bases.append(expr) else: - self.fail('Duplicate base class "%s"' % expr.name, defn) + assert isinstance(expr.node, TypeInfo) + self.fail('Duplicate base class "%s"' % expr.node.name, defn) keys: List[str] = [] types = [] required_keys = set() From 028211b4123748ef5d1a9346577afebd1b17bcda Mon Sep 17 00:00:00 2001 From: Jingchen Ye <97littleleaf11@gmail.com> Date: Mon, 8 Nov 2021 12:24:40 +0800 Subject: [PATCH 3/3] Reorder if-else branches --- mypy/semanal_typeddict.py | 17 +++++++++++++---- test-data/unit/check-typeddict.test | 3 +++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 46565e98cec70..1b076ac4835d8 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -72,16 +72,22 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ typeddict_bases = [] typeddict_bases_set = set() for expr in defn.base_type_exprs: - if (not isinstance(expr, RefExpr) or - expr.fullname not in TPDICT_NAMES and not self.is_typeddict(expr)): - self.fail("All bases of a new TypedDict must be TypedDict types", defn) - elif self.is_typeddict(expr): + if isinstance(expr, RefExpr) and expr.fullname in TPDICT_NAMES: + if 'TypedDict' not in typeddict_bases_set: + typeddict_bases_set.add('TypedDict') + else: + self.fail('Duplicate base class "TypedDict"', defn) + elif isinstance(expr, RefExpr) and self.is_typeddict(expr): + assert expr.fullname if expr.fullname not in typeddict_bases_set: typeddict_bases_set.add(expr.fullname) typeddict_bases.append(expr) else: assert isinstance(expr.node, TypeInfo) self.fail('Duplicate base class "%s"' % expr.node.name, defn) + else: + self.fail("All bases of a new TypedDict must be TypedDict types", defn) + keys: List[str] = [] types = [] required_keys = set() @@ -336,3 +342,6 @@ def is_typeddict(self, expr: Expression) -> bool: def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None: self.api.fail(msg, ctx, code=code) + + def note(self, msg: str, ctx: Context) -> None: + self.api.note(msg, ctx) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index eae26f6c98c00..5fa5514796a01 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -181,6 +181,9 @@ class A(TypedDict): class B(A, A): # E: Duplicate base class "A" z: str + +class C(TypedDict, TypedDict): # E: Duplicate base class "TypedDict" + c1: int [typing fixtures/typing-typeddict.pyi] [case testCannotCreateTypedDictWithClassWithOtherStuff]