Skip to content

[TEST] Support adjacent __new__.__defaults__ for functional namedtuples (#2882)#2882

Open
migeed-z wants to merge 1 commit intofacebook:mainfrom
migeed-z:export-D97825073
Open

[TEST] Support adjacent __new__.__defaults__ for functional namedtuples (#2882)#2882
migeed-z wants to merge 1 commit intofacebook:mainfrom
migeed-z:export-D97825073

Conversation

@migeed-z
Copy link
Contributor

@migeed-z migeed-z commented Mar 23, 2026

Summary:

Support the canonical P.__new__.__defaults__ = (val,) idiom adjacent to functional namedtuple definitions, making trailing fields optional in the constructor.

This is the standard pre-class-syntax way to add defaults to namedtuples:

Point = namedtuple("Point", ["x", "y", "z"])
Point.__new__.__defaults__ = (0,)   # makes z optional
p = Point(1, 2)  # valid — z defaults to 0

Works for both collections.namedtuple and typing.NamedTuple functional forms. Detection uses forward peek in stmts(), only the immediately adjacent statement is checked, matching Pyright's behavior.

  • Only tuple/None literal RHS is consumed; non-literal RHS flows through normal type checking
  • Right-aligned defaults following Python semantics (N defaults → last N fields optional)
  • __new__.__defaults__ replaces any existing defaults= kwarg, matching runtime behavior

Fixes #2611

Differential Revision: D97825073

@meta-cla meta-cla bot added the cla signed label Mar 23, 2026
@meta-codesync
Copy link

meta-codesync bot commented Mar 23, 2026

@migeed-z has exported this pull request. If you are a Meta employee, you can view the originating Diff in D97825073.

@migeed-z migeed-z changed the title Support adjacent __new__.__defaults__ for functional namedtuples [TEST]Support adjacent __new__.__defaults__ for functional namedtuples Mar 23, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

…ebook#2882)

Summary:

Support the canonical `P.__new__.__defaults__ = (val,)` idiom adjacent to functional namedtuple definitions, making trailing fields optional in the constructor.

This is the standard pre-class-syntax way to add defaults to namedtuples:
```python
Point = namedtuple("Point", ["x", "y", "z"])
Point.__new__.__defaults__ = (0,)   # makes z optional
p = Point(1, 2)  # valid — z defaults to 0
```

Works for both `collections.namedtuple` and `typing.NamedTuple` functional forms. Detection uses forward peek in `stmts()`, only the immediately adjacent statement is checked, matching Pyright's behavior.

- Only tuple/None literal RHS is consumed; non-literal RHS flows through normal type checking
- Right-aligned defaults following Python semantics (N defaults → last N fields optional)
- `__new__.__defaults__` replaces any existing `defaults=` kwarg, matching runtime behavior

Fixes facebook#2611

Differential Revision: D97825073
@meta-codesync meta-codesync bot changed the title [TEST]Support adjacent __new__.__defaults__ for functional namedtuples Support adjacent __new__.__defaults__ for functional namedtuples (#2882) Mar 23, 2026
@migeed-z migeed-z changed the title Support adjacent __new__.__defaults__ for functional namedtuples (#2882) [TEST] Support adjacent __new__.__defaults__ for functional namedtuples (#2882) Mar 23, 2026
@github-actions
Copy link

Diff from mypy_primer, showing the effect of this PR on open source code:

cloud-init (https://github.com/canonical/cloud-init)
- ERROR tests/unittests/sources/test_smartos.py:818:12-43: `in` is not supported between `Literal['payload']` and `None` [not-iterable]
- ERROR tests/unittests/sources/test_smartos.py:820:17-46: `None` is not subscriptable [unsupported-operation]
- ERROR tests/unittests/sources/test_smartos.py:824:45-827:14: No matching overload found for function `str.format` called with arguments: (payloadstr=Literal[''] | Unknown, **Unknown | None) [no-matching-overload]
- ERROR tests/unittests/sources/test_smartos.py:836:16-44: `None` is not subscriptable [unsupported-operation]
- ERROR tests/unittests/sources/test_smartos.py:842:5-20: Object of class `NoneType` has no attribute `read` [missing-attribute]

@github-actions
Copy link

Primer Diff Classification

✅ 1 improvement(s) | 1 project(s) total | -5 errors

1 improvement(s) across cloud-init.

Project Verdict Changes Error Kinds Root Cause
cloud-init ✅ Improvement -5 missing-attribute, no-matching-overload synthesize_collections_named_tuple_def()
Detailed analysis

✅ Improvement (1)

cloud-init (-5)

All 5 removed errors were false positives caused by pyrefly incorrectly constraining collections.namedtuple field types based on default values. At line 792, res is assigned the namedtuple class (not an instance) returned by namedtuple(), with defaults=(None, None, None, None, None, None). Pyrefly previously inferred all field types as None based on these defaults. The code then sets class-level attributes like res.response_parts = {...} (a dict), res.serial = m_serial (a MagicMock), res.request_id = 0xABCDEF12 (an int), etc. Since pyrefly still treated these attributes as None type, it produced errors when the code used them as their actual types: checking "payload" in res.response_parts (using in on None), subscripting res.response_parts["payload"] (None not subscriptable), using **res.response_parts in str.format, slicing res.metasource_data[:length] (None not subscriptable), and calling res.serial.read.side_effect (None has no attribute read). The PR correctly changes the behavior so that collections.namedtuple defaults mark fields as optional without constraining their types (using Binding::Any(AnyStyle::Implicit)), which matches runtime semantics since collections.namedtuple fields are inherently untyped.
Attribution: The change in pyrefly/lib/binding/class.rs in synthesize_collections_named_tuple_def() maps defaults to Binding::Any(AnyStyle::Implicit) instead of using the default expression as the field type. This means collections.namedtuple fields with defaults=(None, ...) are now typed as implicit Any rather than None, which removes the cascade of false positives when the fields are subsequently reassigned to non-None values. The stmts() change in pyrefly/lib/binding/bindings.rs also adds adjacent __new__.__defaults__ support, but the key fix for this project is the type inference change for namedtuple defaults.


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (1 LLM)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamically created namedtuple should not be too strict about __init__

1 participant