Drop inherited dataclass default when an override assigns a field specifier - #11664
Open
Nilesh Patil (nileshpatil6) wants to merge 1 commit into
Open
Conversation
…cifier
When a dataclass field overrides an inherited field that has a default,
pyright always re-inherits that default. That matches the runtime only
for a bare annotation. If the override assigns a field specifier that
supplies no default, the runtime replaces the entry outright and the
parameter becomes required:
@DataClass
class Base:
x: int = 1
@DataClass
class Foo(Base):
x: int = field()
inspect.signature(Foo.__init__) # (self, x: int) -> None
Foo() # TypeError: missing argument 'x'
pyright synthesized "(self: Foo, a: int = 0) -> None" instead, so the
call above was not reported, and the override itself was flagged with
"overrides a field of the same name but is missing a default value"
even though nothing is inherited in that case.
Track whether the entry had an assigned value and only inherit the base
default for bare annotations. The bare form is unchanged: it still
inherits and still reports the diagnostic. Verified against CPython for
all three forms, including that the default is dropped for
"x: int = field()", kept for "x: int", and that the parameter is removed
for "x: int = field(init=False)".
Fixes microsoft#11660
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11660.
The bug
When a dataclass field overrides an inherited field that has a default,
dataClasses.tsunconditionally re-inherits that default:That is correct for a bare annotation, but not when the override assigns a field specifier that supplies no default. I checked the three forms against CPython 3.11 rather than relying on the issue report:
So
x: int = field()replaces the inherited entry and the parameter becomes required. pyright instead synthesized(self: Foo, a: int = 0) -> None, which produced two wrong results at once:Foo()was accepted, though it raisesTypeErrorat runtime."x" overrides a field of the same name but is missing a default value, even though in this form nothing is inherited.There is a knock-on ordering case too. When a defaulted field precedes the override, dropping the default makes a non-default parameter follow a default one, which CPython rejects at class creation:
pyright did not report that either. With this change the existing "Fields without default values cannot appear after fields with default values" check fires on that line.
The change
Track whether the entry had an assigned value, and only inherit the base default for bare annotations. Behavior for
x: intis unchanged: it still inherits the default and still reports the existing diagnostic.Tests
Added to
dataclass4.py, the sample for inherited dataclasses. The new cases pin the synthesized signature so they actually discriminate:Without the fix that reports
(self: DC13, a: int = 0) -> Noneand the suite fails; with it, it passes. The expected error count forDataClass4goes from 6 to 8, covering the now-reportedDC13()call and the ordering case.npx jest typeEvaluatorpasses 1212/1212 across all 8 suites, and Prettier reports the changed files clean.