Skip to content

Basic support for typing_extensions.NamedTuple #13178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__()
Expand Down
4 changes: 2 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions test-data/unit/check-class-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
1 change: 1 addition & 0 deletions test-data/unit/lib-stub/typing_extensions.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down