diff --git a/mypy/nodes.py b/mypy/nodes.py index 660adcc63053..b7824cdd079b 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2417,7 +2417,7 @@ class NamedTupleExpr(Expression): # The class representation of this named tuple (its tuple_type attribute contains # the tuple item types) info: "TypeInfo" - is_typed: bool # whether this class was created with typing.NamedTuple + is_typed: bool # whether this class was created with typing(_extensions).NamedTuple def __init__(self, info: 'TypeInfo', is_typed: bool = False) -> None: super().__init__() diff --git a/mypy/semanal.py b/mypy/semanal.py index 6e77fea2f9a1..6bfa6e7783c4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -99,7 +99,7 @@ TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeVarLikeType, Parameters, ParamSpecType, PROTOCOL_NAMES, TYPE_ALIAS_NAMES, FINAL_TYPE_NAMES, FINAL_DECORATOR_NAMES, REVEAL_TYPE_NAMES, - ASSERT_TYPE_NAMES, OVERLOAD_NAMES, is_named_instance, + ASSERT_TYPE_NAMES, OVERLOAD_NAMES, TYPED_NAMEDTUPLE_NAMES, is_named_instance, ) from mypy.typeops import function_type, get_type_vars from mypy.type_visitor import TypeQuery @@ -1529,7 +1529,7 @@ def analyze_base_classes( bases = [] for base_expr in base_type_exprs: if (isinstance(base_expr, RefExpr) and - base_expr.fullname in ('typing.NamedTuple',) + TPDICT_NAMES): + base_expr.fullname in TYPED_NAMEDTUPLE_NAMES + TPDICT_NAMES): # Ignore magic bases for now. continue diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index ef0a38d22277..e63be5387810 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -9,7 +9,7 @@ from mypy.types import ( Type, TupleType, AnyType, TypeOfAny, CallableType, TypeType, TypeVarType, - UnboundType, LiteralType, + UnboundType, LiteralType, TYPED_NAMEDTUPLE_NAMES ) from mypy.semanal_shared import ( SemanticAnalyzerInterface, set_callable_name, calculate_tuple_fallback, PRIORITY_FALLBACKS @@ -65,7 +65,7 @@ def analyze_namedtuple_classdef(self, defn: ClassDef, is_stub_file: bool, for base_expr in defn.base_type_exprs: if isinstance(base_expr, RefExpr): self.api.accept(base_expr) - if base_expr.fullname == 'typing.NamedTuple': + if base_expr.fullname in TYPED_NAMEDTUPLE_NAMES: result = self.check_namedtuple_classdef(defn, is_stub_file) if result is None: # This is a valid named tuple, but some types are incomplete. @@ -175,7 +175,7 @@ def check_namedtuple(self, fullname = callee.fullname if fullname == 'collections.namedtuple': is_typed = False - elif fullname == 'typing.NamedTuple': + elif fullname in TYPED_NAMEDTUPLE_NAMES: is_typed = True else: return None, None @@ -270,7 +270,7 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str Return None if the definition didn't typecheck. """ - type_name = 'NamedTuple' if fullname == 'typing.NamedTuple' else 'namedtuple' + type_name = 'NamedTuple' if fullname in TYPED_NAMEDTUPLE_NAMES else 'namedtuple' # TODO: Share code with check_argument_count in checkexpr.py? args = call.args if len(args) < 2: @@ -279,7 +279,7 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str defaults: List[Expression] = [] if len(args) > 2: # Typed namedtuple doesn't support additional arguments. - if fullname == 'typing.NamedTuple': + if fullname in TYPED_NAMEDTUPLE_NAMES: self.fail('Too many arguments for "NamedTuple()"', call) return None for i, arg_name in enumerate(call.arg_names[2:], 2): diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 8b7b3153ecaf..1e76912de92a 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -8,7 +8,7 @@ Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance, FunctionLike, TypeOfAny, LiteralType, get_proper_type, TypeAliasType, ParamSpecType, - Parameters, UnpackType, TUPLE_LIKE_INSTANCE_NAMES, TypeVarTupleType, + Parameters, UnpackType, TUPLE_LIKE_INSTANCE_NAMES, TYPED_NAMEDTUPLE_NAMES, TypeVarTupleType, ) import mypy.applytype import mypy.constraints @@ -286,7 +286,7 @@ def visit_instance(self, left: Instance) -> bool: # in `TypeInfo.mro`, so when `(a: NamedTuple) -> None` is used, # we need to check for `is_named_tuple` property if ((left.type.has_base(rname) or rname == 'builtins.object' - or (rname == 'typing.NamedTuple' + or (rname in TYPED_NAMEDTUPLE_NAMES and any(l.is_named_tuple for l in left.type.mro))) and not self.ignore_declared_variance): # Map left type to corresponding right instances. diff --git a/mypy/types.py b/mypy/types.py index 5abd9fcad03b..a70c6885dff5 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -66,6 +66,11 @@ SyntheticTypeVisitor as SyntheticTypeVisitor, ) +TYPED_NAMEDTUPLE_NAMES: Final = ( + "typing.NamedTuple", + "typing_extensions.NamedTuple" +) + # Supported names of TypedDict type constructors. TPDICT_NAMES: Final = ( "typing.TypedDict", diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 4a1a84ca0301..6701a5ebfbcc 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -709,3 +709,21 @@ class HasStaticMethod(NamedTuple): return 4 [builtins fixtures/property.pyi] + +[case testTypingExtensionsNamedTuple] +from typing_extensions import NamedTuple + +class Point(NamedTuple): + x: int + y: int + +bad_point = Point('foo') # E: Missing positional argument "y" in call to "Point" \ + # E: Argument 1 to "Point" has incompatible type "str"; expected "int" +point = Point(1, 2) +x, y = point +x = point.x +reveal_type(x) # N: Revealed type is "builtins.int" +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] diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index d4c3244cf083..b82b73d49a71 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -10,6 +10,7 @@ class _SpecialForm: def __getitem__(self, typeargs: Any) -> Any: pass +NamedTuple = 0 Protocol: _SpecialForm = ... def runtime_checkable(x: _T) -> _T: pass runtime = runtime_checkable