diff --git a/mypy/semanal.py b/mypy/semanal.py index 790c0c6d96d7..dfffaf4a71f1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1109,7 +1109,8 @@ def analyze_namedtuple_classdef(self, defn: ClassDef) -> bool: # in the named tuple class body. is_named_tuple, info = True, defn.info # type: bool, Optional[TypeInfo] else: - is_named_tuple, info = self.named_tuple_analyzer.analyze_namedtuple_classdef(defn) + is_named_tuple, info = self.named_tuple_analyzer.analyze_namedtuple_classdef( + defn, self.is_stub_file) if is_named_tuple: if info is None: self.mark_incomplete(defn.name, defn) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index e0ba44885cfa..da65f6f063d2 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -41,7 +41,8 @@ def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None: self.options = options self.api = api - def analyze_namedtuple_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[TypeInfo]]: + def analyze_namedtuple_classdef(self, defn: ClassDef, is_stub_file: bool + ) -> Tuple[bool, Optional[TypeInfo]]: """Analyze if given class definition can be a named tuple definition. Return a tuple where first item indicates whether this can possibly be a named tuple, @@ -52,7 +53,7 @@ def analyze_namedtuple_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Ty if isinstance(base_expr, RefExpr): self.api.accept(base_expr) if base_expr.fullname == 'typing.NamedTuple': - result = self.check_namedtuple_classdef(defn) + result = self.check_namedtuple_classdef(defn, is_stub_file) if result is None: # This is a valid named tuple, but some types are incomplete. return True, None @@ -68,8 +69,10 @@ def analyze_namedtuple_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Ty # This can't be a valid named tuple. return False, None - def check_namedtuple_classdef( - self, defn: ClassDef) -> Optional[Tuple[List[str], List[Type], Dict[str, Expression]]]: + def check_namedtuple_classdef(self, defn: ClassDef, is_stub_file: bool + ) -> Optional[Tuple[List[str], + List[Type], + Dict[str, Expression]]]: """Parse and validate fields in named tuple class definition. Return a three tuple: @@ -78,7 +81,7 @@ def check_namedtuple_classdef( * field default values or None, if any of the types are not ready. """ - if self.options.python_version < (3, 6): + if self.options.python_version < (3, 6) and not is_stub_file: self.fail('NamedTuple class syntax is only supported in Python 3.6', defn) return [], [], {} if len(defn.base_type_exprs) > 1: diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 6d8626456403..c215fb7d7a97 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -44,6 +44,24 @@ x.x x.y x.z # E: "X" has no attribute "z" +[case testNamedTupleClassPython35] +# flags: --python-version 3.5 +from typing import NamedTuple + +class A(NamedTuple): + x = 3 # type: int +[out] +main:4: error: NamedTuple class syntax is only supported in Python 3.6 + +[case testNamedTupleClassInStubPython35] +# flags: --python-version 3.5 +import foo + +[file foo.pyi] +from typing import NamedTuple + +class A(NamedTuple): + x: int [case testNamedTupleAttributesAreReadOnly] from collections import namedtuple