diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index a9f12ceae5c2..b197eda0d365 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -166,6 +166,10 @@ def check_namedtuple_classdef( # And docstrings. if isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr): continue + # Allow nested classes. + if isinstance(stmt, ClassDef): + self.api.analyze_class(stmt) + continue statements.pop() defn.removed_statements.append(stmt) self.fail(NAMEDTUP_CLASS_ERROR, stmt) diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index c86ed828b2b9..eb6d3418155d 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -251,6 +251,10 @@ def process_placeholder( ) -> None: raise NotImplementedError + @abstractmethod + def analyze_class(self, defn: ClassDef) -> None: + raise NotImplementedError + def set_callable_name(sig: Type, fdef: FuncDef) -> ProperType: sig = get_proper_type(sig) diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 8ae7f6555f9d..c82f453a6e7f 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -728,3 +728,23 @@ reveal_type(y) # N: Revealed type is "builtins.int" point.y = 6 # E: Property "y" defined in "Point" is read-only [builtins fixtures/tuple.pyi] + +[case testNestedNamedTuple] +from enum import Enum +from typing import NamedTuple + +class T(NamedTuple): + class State(Enum): + A = 1 + state: State + +class RaggedFeature(NamedTuple): + class RowSplits(NamedTuple): + x: int + + splits: RowSplits + +reveal_type(T.State.A) # N: Revealed type is "Literal[__main__.State.A]?" +reveal_type(RaggedFeature.RowSplits.x) # N: Revealed type is "builtins.int" + +[builtins fixtures/tuple.pyi]