diff --git a/conformance/results/mypy/namedtuples_define_class.toml b/conformance/results/mypy/namedtuples_define_class.toml index adf590b4..575c5c0c 100644 --- a/conformance/results/mypy/namedtuples_define_class.toml +++ b/conformance/results/mypy/namedtuples_define_class.toml @@ -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]'] """ diff --git a/conformance/results/mypy/namedtuples_define_functional.toml b/conformance/results/mypy/namedtuples_define_functional.toml index 151e6588..6356dc8c 100644 --- a/conformance/results/mypy/namedtuples_define_functional.toml +++ b/conformance/results/mypy/namedtuples_define_functional.toml @@ -11,7 +11,8 @@ 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:69: error: Missing positional argument "a" in call to "NT7" [call-arg] """ conformance_automated = "Pass" errors_diff = """ diff --git a/conformance/results/pyrefly/namedtuples_define_class.toml b/conformance/results/pyrefly/namedtuples_define_class.toml index fd191957..0d85f64e 100644 --- a/conformance/results/pyrefly/namedtuples_define_class.toml +++ b/conformance/results/pyrefly/namedtuples_define_class.toml @@ -1,6 +1,10 @@ -conformant = "Pass" -conformance_automated = "Pass" +conformant = "Partial" +notes = """ +Doesn't catch illegal field name starting with underscore +""" +conformance_automated = "Fail" errors_diff = """ +Line 76: Expected 1 errors """ output = """ ERROR namedtuples_define_class.py:32:7-12: Index 3 out of range for tuple with 3 elements [index-error] @@ -11,8 +15,9 @@ ERROR namedtuples_define_class.py:46:15-17: Argument `Literal['']` is not assign ERROR namedtuples_define_class.py:47:24-25: Argument `Literal[3]` is not assignable to parameter `units` with type `str` in function `Point.__new__` [bad-argument-type] ERROR namedtuples_define_class.py:48:22-24: Expected 3 positional arguments, got 4 in function `Point.__new__` [bad-argument-count] ERROR namedtuples_define_class.py:49:23-28: Unexpected keyword argument `other` in function `Point.__new__` [unexpected-keyword] -ERROR namedtuples_define_class.py:59:5-13: NamedTuple field 'latitude' without a default may not follow NamedTuple field with a default [bad-class-definition] -ERROR namedtuples_define_class.py:79:5-6: Cannot override named tuple element `x` [bad-override] -ERROR namedtuples_define_class.py:98:19-22: Argument `float` is not assignable to parameter `value` with type `str` in function `Property.__new__` [bad-argument-type] -ERROR namedtuples_define_class.py:105:7-11: Named tuples do not support multiple inheritance [invalid-inheritance] +ERROR namedtuples_define_class.py:69:20-22: Expected 2 positional arguments, got 3 in function `Point2.__new__` [bad-argument-count] +ERROR namedtuples_define_class.py:86:5-13: NamedTuple field 'latitude' without a default may not follow NamedTuple field with a default [bad-class-definition] +ERROR namedtuples_define_class.py:106:5-6: Cannot override named tuple element `x` [bad-override] +ERROR namedtuples_define_class.py:125:19-22: Argument `float` is not assignable to parameter `value` with type `str` in function `Property.__new__` [bad-argument-type] +ERROR namedtuples_define_class.py:132:7-11: Named tuples do not support multiple inheritance [invalid-inheritance] """ diff --git a/conformance/results/pyrefly/namedtuples_define_functional.toml b/conformance/results/pyrefly/namedtuples_define_functional.toml index 35a38690..40c47f73 100644 --- a/conformance/results/pyrefly/namedtuples_define_functional.toml +++ b/conformance/results/pyrefly/namedtuples_define_functional.toml @@ -16,5 +16,6 @@ ERROR namedtuples_define_functional.py:43:17-20: Argument `float` is not assigna ERROR namedtuples_define_functional.py:52:31-34: Duplicate field `a` [bad-class-definition] ERROR namedtuples_define_functional.py:53:33-38: `def` is not a valid identifier [bad-class-definition] ERROR namedtuples_define_functional.py:54:33-38: `def` is not a valid identifier [bad-class-definition] -ERROR namedtuples_define_functional.py:66:4-6: Missing argument `a` in function `NT5.__new__` [missing-argument] +ERROR namedtuples_define_functional.py:55:33-37: NamedTuple field name may not start with an underscore: `_d` [bad-class-definition] +ERROR namedtuples_define_functional.py:69:4-6: Missing argument `a` in function `NT7.__new__` [missing-argument] """ diff --git a/conformance/results/pyright/namedtuples_define_class.toml b/conformance/results/pyright/namedtuples_define_class.toml index 19f9aadc..e04b26fc 100644 --- a/conformance/results/pyright/namedtuples_define_class.toml +++ b/conformance/results/pyright/namedtuples_define_class.toml @@ -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 = """ diff --git a/conformance/results/pyright/namedtuples_define_functional.toml b/conformance/results/pyright/namedtuples_define_functional.toml index 07c43204..266f4083 100644 --- a/conformance/results/pyright/namedtuples_define_functional.toml +++ b/conformance/results/pyright/namedtuples_define_functional.toml @@ -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) @@ -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)'] """ diff --git a/conformance/results/results.html b/conformance/results/results.html index c632a117..79ecbdd8 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -915,11 +915,11 @@

Python Type System Conformance Test Results

Partial

Does not reject override of named tuple attribute in child class.

Pass
Partial

Does not reject override of named tuple attribute in child class.

-Pass +
Partial

Doesn't catch illegal field name starting with underscore

     namedtuples_define_functional Pass -Pass +Partial Pass Pass diff --git a/conformance/results/zuban/namedtuples_define_class.toml b/conformance/results/zuban/namedtuples_define_class.toml index 397c708f..67acdb24 100644 --- a/conformance/results/zuban/namedtuples_define_class.toml +++ b/conformance/results/zuban/namedtuples_define_class.toml @@ -4,7 +4,8 @@ Does not reject override of named tuple attribute in child class. """ conformance_automated = "Fail" errors_diff = """ -Line 79: 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]'] """ output = """ namedtuples_define_class.py:32: error: Tuple index out of range [misc] @@ -15,7 +16,10 @@ 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:69: error: Too many arguments for "Point2" [call-arg] +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] """ diff --git a/conformance/results/zuban/namedtuples_define_functional.toml b/conformance/results/zuban/namedtuples_define_functional.toml index 5a604166..98ee2d2f 100644 --- a/conformance/results/zuban/namedtuples_define_functional.toml +++ b/conformance/results/zuban/namedtuples_define_functional.toml @@ -13,5 +13,6 @@ 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:69: error: Missing positional argument "a" in call to "NT7" [call-arg] """ diff --git a/conformance/tests/namedtuples_define_class.py b/conformance/tests/namedtuples_define_class.py index e345a197..5e6e9c4c 100644 --- a/conformance/tests/namedtuples_define_class.py +++ b/conformance/tests/namedtuples_define_class.py @@ -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:: diff --git a/conformance/tests/namedtuples_define_functional.py b/conformance/tests/namedtuples_define_functional.py index 432dff73..49f7c1b0 100644 --- a/conformance/tests/namedtuples_define_functional.py +++ b/conformance/tests/namedtuples_define_functional.py @@ -43,24 +43,27 @@ 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 # > specifies default values for the fields. Type checkers may support this. -NT5 = namedtuple("NT5", "a b c", defaults=(1, 2)) -NT5(1) # OK -NT5(1, 2, 3) # OK -NT5() # E: too few arguments +NT7 = namedtuple("NT7", "a b c", defaults=(1, 2)) +NT7(1) # OK +NT7(1, 2, 3) # OK +NT7() # E: too few arguments diff --git a/docs/spec/namedtuples.rst b/docs/spec/namedtuples.rst index 1ac14136..936d2fa7 100644 --- a/docs/spec/namedtuples.rst +++ b/docs/spec/namedtuples.rst @@ -20,6 +20,14 @@ Type checkers should support the class syntax:: y: int units: str = "meters" +Fields must be annotated attributes - methods and un-annotated attributes are not +considered fields. Field names may not start with an underscore. + + class MyTuple(NamedTuple): + x1 = 1 # Not a field + def x2() -> None: pass # Not a field + _x3: int # Type error: illegal field name + Regardless of whether the class syntax or factory function call is used to define a named tuple, type checkers should synthesize a ``__new__`` method based on the named tuple fields. This mirrors the runtime behavior. In the example @@ -79,17 +87,21 @@ A type checker may support the factory function call in its various forms:: Point5 = NamedTuple('Point5', [('x', int), ('y', int)]) Point6 = NamedTuple('Point6', (('x', int), ('y', int))) -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"]) # Type error (duplicate field name) NT2 = namedtuple("NT2", ["abc", "def"], rename=False) # Type error (illegal field name) + NT3 = namedtuple("NT3", ["abc", "_d"], rename=False) # Type error (illegal field name) + + NT4 = namedtuple("NT4", ["abc", "def"], rename=True) # OK + NT4(abc="", _1="") # OK - NT3 = namedtuple("NT3", ["abc", "def"], rename=True) # OK - NT3(abc="", _1="") # OK + NT5 = namedtuple("NT5", ["abc", "_d"], rename=True) # OK + NT5(abc="", _1="") # OK The ``namedtuple`` function also supports a ``defaults`` keyword argument that specifies default values for the fields. Type checkers may support this::