Skip to content

Clarify namedtuple member rules #1979

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
16 changes: 12 additions & 4 deletions conformance/results/mypy/namedtuples_define_class.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@ namedtuples_define_class.py:46: error: Argument 2 to "Point" has incompatible ty
namedtuples_define_class.py:47: error: Argument "units" to "Point" has incompatible type "int"; expected "str" [arg-type]
namedtuples_define_class.py:48: error: Too many arguments for "Point" [call-arg]
namedtuples_define_class.py:49: error: Unexpected keyword argument "other" for "Point" [call-arg]
namedtuples_define_class.py:59: error: Non-default NamedTuple fields cannot follow default fields [misc]
namedtuples_define_class.py:98: error: Argument 2 to "Property" has incompatible type "float"; expected "str" [arg-type]
namedtuples_define_class.py:105: error: NamedTuple should be a single base [misc]
namedtuples_define_class.py:59: error: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" [misc]
namedtuples_define_class.py:65: error: Missing positional argument "units" in call to "Point2" [call-arg]
namedtuples_define_class.py:67: error: Too many values to unpack (2 expected, 3 provided) [misc]
namedtuples_define_class.py:76: error: NamedTuple field name cannot start with an underscore: _y [misc]
namedtuples_define_class.py:86: error: Non-default NamedTuple fields cannot follow default fields [misc]
namedtuples_define_class.py:125: error: Argument 2 to "Property" has incompatible type "float"; expected "str" [arg-type]
namedtuples_define_class.py:132: error: NamedTuple should be a single base [misc]
"""
conformance_automated = "Fail"
errors_diff = """
Line 79: Expected 1 errors
Line 69: Expected 1 errors
Line 106: Expected 1 errors
Line 59: Unexpected errors ['namedtuples_define_class.py:59: error: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" [misc]']
Line 65: Unexpected errors ['namedtuples_define_class.py:65: error: Missing positional argument "units" in call to "Point2" [call-arg]']
Line 67: Unexpected errors ['namedtuples_define_class.py:67: error: Too many values to unpack (2 expected, 3 provided) [misc]']
"""
13 changes: 10 additions & 3 deletions conformance/results/mypy/namedtuples_define_functional.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
conformant = "Pass"
conformant = "Partial"
output = """
namedtuples_define_functional.py:16: error: Missing positional argument "y" in call to "Point1" [call-arg]
namedtuples_define_functional.py:21: error: Missing positional arguments "x", "y" in call to "Point2" [call-arg]
Expand All @@ -11,8 +11,15 @@ namedtuples_define_functional.py:43: error: Argument "x" to "Point6" has incompa
namedtuples_define_functional.py:52: error: "namedtuple()" has duplicate field name "a" [misc]
namedtuples_define_functional.py:53: error: "namedtuple()" field name "def" is a keyword [misc]
namedtuples_define_functional.py:54: error: "namedtuple()" field name "def" is a keyword [misc]
namedtuples_define_functional.py:66: error: Missing positional argument "a" in call to "NT5" [call-arg]
namedtuples_define_functional.py:55: error: "namedtuple()" field name "_d" starts with an underscore [misc]
namedtuples_define_functional.py:66: error: Name "NT5" already defined on line 57 [no-redef]
namedtuples_define_functional.py:67: error: Missing positional argument "_1" in call to "NT5" [call-arg]
namedtuples_define_functional.py:68: error: Too many arguments for "NT5" [call-arg]
namedtuples_define_functional.py:69: error: Missing positional arguments "abc", "_1" in call to "NT5" [call-arg]
"""
conformance_automated = "Pass"
conformance_automated = "Fail"
errors_diff = """
Line 66: Unexpected errors ['namedtuples_define_functional.py:66: error: Name "NT5" already defined on line 57 [no-redef]']
Line 67: Unexpected errors ['namedtuples_define_functional.py:67: error: Missing positional argument "_1" in call to "NT5" [call-arg]']
Line 68: Unexpected errors ['namedtuples_define_functional.py:68: error: Too many arguments for "NT5" [call-arg]']
"""
2 changes: 1 addition & 1 deletion conformance/results/mypy/version.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version = "mypy 1.16.1"
test_duration = 2.2
test_duration = 2.0
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ notes = """
Reports error for legitimate use of `...` to specialize ParamSpec
"""
output = """
generics_paramspec_specialization.py:32:14 Invalid type parameters [24]: Callable parameters expected for parameter specification `P2`, but a single type `...` was given for generic type ClassB.
generics_paramspec_specialization.py:36:14 Invalid type parameters [24]: Callable parameters expected for parameter specification `P1`, but a single type `...` was given for generic type ClassA.
generics_paramspec_specialization.py:44:14 Invalid type parameters [24]: Callable parameters expected for parameter specification `P1`, but a single type `int` was given for generic type ClassA.
generics_paramspec_specialization.py:53:4 Too many arguments [19]: PositionalOnly call expects 2 positional arguments, 3 were provided.
generics_paramspec_specialization.py:54:4 Too many arguments [19]: PositionalOnly call expects 2 positional arguments, 3 were provided.
Expand All @@ -15,8 +13,6 @@ generics_paramspec_specialization.py:61:4 Too many arguments [19]: PositionalOnl
"""
conformance_automated = "Fail"
errors_diff = """
Line 32: Unexpected errors ['generics_paramspec_specialization.py:32:14 Invalid type parameters [24]: Callable parameters expected for parameter specification `P2`, but a single type `...` was given for generic type ClassB.']
Line 36: Unexpected errors ['generics_paramspec_specialization.py:36:14 Invalid type parameters [24]: Callable parameters expected for parameter specification `P1`, but a single type `...` was given for generic type ClassA.']
Line 53: Unexpected errors ['generics_paramspec_specialization.py:53:4 Too many arguments [19]: PositionalOnly call expects 2 positional arguments, 3 were provided.']
Line 59: Unexpected errors ['generics_paramspec_specialization.py:59:4 Too many arguments [19]: PositionalOnly call expects 2 positional arguments, 3 were provided.']
"""
14 changes: 9 additions & 5 deletions conformance/results/pyre/namedtuples_define_class.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ namedtuples_define_class.py:46:14 Incompatible parameter type [6]: In call `Poin
namedtuples_define_class.py:47:17 Incompatible parameter type [6]: In call `Point.__init__`, for argument `units`, expected `str` but got `int`.
namedtuples_define_class.py:48:5 Too many arguments [19]: Call `Point.__init__` expects 3 positional arguments, 4 were provided.
namedtuples_define_class.py:49:6 Unexpected keyword [28]: Unexpected keyword argument `other` to call `Point.__init__`.
namedtuples_define_class.py:59:4 Missing named tuple default [74]: Named tuple field without default value may not be preceded by a field with default value.
namedtuples_define_class.py:79:4 Invalid assignment [41]: Cannot reassign final attribute `x`.
namedtuples_define_class.py:98:18 Incompatible parameter type [6]: In call `Property.__init__`, for 2nd positional argument, expected `str` but got `float`.
namedtuples_define_class.py:105:23 Invalid inheritance [39]: If NamedTuple is included as a base class, the class may not extend anything else besides Generic.
namedtuples_define_class.py:67:0 Unable to unpack [23]: Unable to unpack 3 values, 2 were expected.
namedtuples_define_class.py:86:4 Missing named tuple default [74]: Named tuple field without default value may not be preceded by a field with default value.
namedtuples_define_class.py:106:4 Invalid assignment [41]: Cannot reassign final attribute `x`.
namedtuples_define_class.py:125:18 Incompatible parameter type [6]: In call `Property.__init__`, for 2nd positional argument, expected `str` but got `float`.
namedtuples_define_class.py:132:23 Invalid inheritance [39]: If NamedTuple is included as a base class, the class may not extend anything else besides Generic.
"""
conformance_automated = "Pass"
conformance_automated = "Fail"
errors_diff = """
Line 69: Expected 1 errors
Line 76: Expected 1 errors
Line 67: Unexpected errors ['namedtuples_define_class.py:67:0 Unable to unpack [23]: Unable to unpack 3 values, 2 were expected.']
"""
10 changes: 7 additions & 3 deletions conformance/results/pyre/namedtuples_define_functional.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
conformant = "Pass"
conformant = "Partial"
notes = """
"""
output = """
Expand All @@ -12,8 +12,12 @@ namedtuples_define_functional.py:42:17 Incompatible parameter type [6]: In call
namedtuples_define_functional.py:43:14 Incompatible parameter type [6]: In call `Point6.__init__`, for argument `x`, expected `int` but got `float`.
namedtuples_define_functional.py:52:0 Duplicate parameter [65]: Duplicate parameter name `a`.
namedtuples_define_functional.py:52:0 Duplicate parameter [65]: Duplicate parameter name `a`.
namedtuples_define_functional.py:66:0 Missing argument [20]: Call `NT5.__init__` expects argument `a`.
namedtuples_define_functional.py:58:0 Unexpected keyword [28]: Unexpected keyword argument `abc` to call `NT5.__init__`.
namedtuples_define_functional.py:60:0 Unexpected keyword [28]: Unexpected keyword argument `_1` to call `NT6.__init__`.
namedtuples_define_functional.py:69:0 Missing argument [20]: Call `NT5.__init__` expects argument `a`.
"""
conformance_automated = "Pass"
conformance_automated = "Fail"
errors_diff = """
Line 58: Unexpected errors ['namedtuples_define_functional.py:58:0 Unexpected keyword [28]: Unexpected keyword argument `abc` to call `NT5.__init__`.']
Line 60: Unexpected errors ['namedtuples_define_functional.py:60:0 Unexpected keyword [28]: Unexpected keyword argument `_1` to call `NT6.__init__`.']
"""
4 changes: 2 additions & 2 deletions conformance/results/pyre/version.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version = "pyre 0.9.23"
test_duration = 10.7
version = "pyre 0.9.25"
test_duration = 7.6
10 changes: 6 additions & 4 deletions conformance/results/pyright/namedtuples_define_class.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ namedtuples_define_class.py:47:24 - error: Argument of type "Literal[3]" cannot
  "Literal[3]" is not assignable to "str" (reportArgumentType)
namedtuples_define_class.py:48:22 - error: Expected 3 positional arguments (reportCallIssue)
namedtuples_define_class.py:49:23 - error: No parameter named "other" (reportCallIssue)
namedtuples_define_class.py:59:5 - error: Fields without default values cannot appear after fields with default values (reportGeneralTypeIssues)
namedtuples_define_class.py:79:5 - error: Cannot override "x" because parent class "Point" is a named tuple (reportIncompatibleVariableOverride)
namedtuples_define_class.py:98:19 - error: Argument of type "float" cannot be assigned to parameter "value" of type "str" in function "__new__"
namedtuples_define_class.py:69:20 - error: Expected 2 positional arguments (reportCallIssue)
namedtuples_define_class.py:76:5 - error: Named tuple field names cannot start with an underscore (reportGeneralTypeIssues)
namedtuples_define_class.py:86:5 - error: Fields without default values cannot appear after fields with default values (reportGeneralTypeIssues)
namedtuples_define_class.py:106:5 - error: Cannot override "x" because parent class "Point" is a named tuple (reportIncompatibleVariableOverride)
namedtuples_define_class.py:125:19 - error: Argument of type "float" cannot be assigned to parameter "value" of type "str" in function "__new__"
  "float" is not assignable to "str" (reportArgumentType)
namedtuples_define_class.py:105:7 - error: Multiple inheritance with NamedTuple is not supported (reportGeneralTypeIssues)
namedtuples_define_class.py:132:7 - error: Multiple inheritance with NamedTuple is not supported (reportGeneralTypeIssues)
"""
conformance_automated = "Pass"
errors_diff = """
Expand Down
11 changes: 8 additions & 3 deletions conformance/results/pyright/namedtuples_define_functional.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
conformant = "Pass"
conformant = "Partial"
output = """
namedtuples_define_functional.py:16:8 - error: Argument missing for parameter "y" (reportCallIssue)
namedtuples_define_functional.py:21:8 - error: Arguments missing for parameters "x", "y" (reportCallIssue)
Expand All @@ -15,8 +15,13 @@ namedtuples_define_functional.py:43:17 - error: Argument of type "float" cannot
namedtuples_define_functional.py:52:31 - error: Names within a named tuple must be unique (reportGeneralTypeIssues)
namedtuples_define_functional.py:53:33 - error: Field names cannot be a keyword (reportGeneralTypeIssues)
namedtuples_define_functional.py:54:33 - error: Field names cannot be a keyword (reportGeneralTypeIssues)
namedtuples_define_functional.py:66:1 - error: Argument missing for parameter "a" (reportCallIssue)
namedtuples_define_functional.py:55:33 - error: Named tuple field names cannot start with an underscore (reportGeneralTypeIssues)
namedtuples_define_functional.py:59:33 - error: Named tuple field names cannot start with an underscore (reportGeneralTypeIssues)
namedtuples_define_functional.py:60:13 - error: No parameter named "_1" (reportCallIssue)
namedtuples_define_functional.py:69:1 - error: Argument missing for parameter "a" (reportCallIssue)
"""
conformance_automated = "Pass"
conformance_automated = "Fail"
errors_diff = """
Line 59: Unexpected errors ['namedtuples_define_functional.py:59:33 - error: Named tuple field names cannot start with an underscore (reportGeneralTypeIssues)']
Line 60: Unexpected errors ['namedtuples_define_functional.py:60:13 - error: No parameter named "_1" (reportCallIssue)']
"""
4 changes: 2 additions & 2 deletions conformance/results/pyright/version.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version = "pyright 1.1.402"
test_duration = 1.8
version = "pyright 1.1.403"
test_duration = 1.4
14 changes: 7 additions & 7 deletions conformance/results/results.html
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,13 @@ <h3>Python Type System Conformance Test Results</h3>
<div class="table_container"><table><tbody>
<tr><th class="col1">&nbsp;</th>
<th class='tc-header'><div class='tc-name'>mypy 1.16.1</div>
<div class='tc-time'>2.2sec</div>
<div class='tc-time'>2.0sec</div>
</th>
<th class='tc-header'><div class='tc-name'>pyright 1.1.402</div>
<div class='tc-time'>1.8sec</div>
<th class='tc-header'><div class='tc-name'>pyright 1.1.403</div>
<div class='tc-time'>1.4sec</div>
</th>
<th class='tc-header'><div class='tc-name'>pyre 0.9.23</div>
<div class='tc-time'>10.7sec</div>
<th class='tc-header'><div class='tc-name'>pyre 0.9.25</div>
<div class='tc-time'>7.6sec</div>
</th>
</tr>
<tr><th class="column" colspan="4">
Expand Down Expand Up @@ -324,7 +324,7 @@ <h3>Python Type System Conformance Test Results</h3>
</tr>
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;generics_type_erasure</th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Infers Node[Never] instead of Node[Any] when argument is not provided.</p><p>False negative on instance attribute access on type(node).</p></span></div></th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Missing error regarding `type(instance).generic_attribute`.</p></span></div></th>
<th class="column col2 conformant">Pass</th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not erase unspecified type variables to `Any` prior to `assert_type` handling.</p><p>False negatives on instance attribute access on the type.</p><p>Does not infer type of `DefaultDict` with explicit type parameters on constructor.</p><p>False negatives on assert_type uses.</p></span></div></th>
</tr>
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;generics_typevartuple_args</th>
Expand Down Expand Up @@ -423,7 +423,7 @@ <h3>Python Type System Conformance Test Results</h3>
</tr>
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aliases_newtype</th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>`NewType`s are considered classes, not functions.</p></span></div></th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>`NewType`s are considered classes, not functions.</p></span></div></th>
<th class="column col2 conformant">Pass</th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not reject use of NewType in `isinstance` call.</p><p>Does not reject use of NewType in class definition statement.</p><p>Does not report inconsistency between name of NewType and assigned identifier name.</p><p>Does not reject use of NewType with generic class with TypeVar.</p><p>Does not reject use of NewType with protocol class.</p><p>Does not reject use of NewType with TypedDict class.</p><p>Does not reject use of NewType with Any.</p></span></div></th>
</tr>
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aliases_recursive</th>
Expand Down
27 changes: 27 additions & 0 deletions conformance/tests/namedtuples_define_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,33 @@ class Point(NamedTuple):
p10 = Point(1, 2, "", other="") # E


# > Fields must be annotated attributes - methods and un-annotated attributes are not
# > considered fields.


class Point2(NamedTuple):
x: int
y: int
units = "meters" # Not a field

def is_origin(self) -> int: # Not a field
return self.x == 0 and self.y == 0


p11 = Point2(1, 2)
assert_type(p11, Point2)
x, y = p11

p12 = Point2(1, 2, "") # E


# > Field names may not start with an underscore.

class Point3(NamedTuple):
x: int
_y: int # E: illegal field name


# > The runtime implementation of ``NamedTuple`` enforces that fields with default
# > values must come after fields without default values. Type checkers should
# > likewise enforce this restriction::
Expand Down
17 changes: 10 additions & 7 deletions conformance/tests/namedtuples_define_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,21 @@
p6_4 = Point6(x=1.1, y=2) # E


# > At runtime, the ``namedtuple`` function disallows field names that are
# > illegal Python identifiers and either raises an exception or replaces these
# > fields with a parameter name of the form ``_N``. The behavior depends on
# > the value of the ``rename`` argument. Type checkers may replicate this
# > behavior statically.
# > At runtime, the ``namedtuple`` function disallows field names that begin with
# > an underscore or are illegal Python identifiers, and either raises an exception
# > or replaces these fields with a parameter name of the form ``_N``. The behavior
# > depends on the value of the ``rename`` argument. Type checkers may replicate
# > this behavior statically.

NT1 = namedtuple("NT1", ["a", "a"]) # E?: duplicate field name
NT2 = namedtuple("NT2", ["abc", "def"]) # E?: illegal field name
NT3 = namedtuple("NT3", ["abc", "def"], rename=False) # E?: illegal field name
NT4 = namedtuple("NT4", ["abc", "_d"], rename=False) # E?: illegal field name

NT4 = namedtuple("NT4", ["abc", "def"], rename=True) # OK
NT4(abc="", _1="") # OK
NT5 = namedtuple("NT5", ["abc", "def"], rename=True) # OK
NT5(abc="", _1="") # OK
NT6 = namedtuple("NT6", ["abc", "_d"], rename=True) # OK
NT6(abc="", _1="") # OK


# > The ``namedtuple`` function also supports a ``defaults`` keyword argument that
Expand Down
Loading