Skip to content

Commit

Permalink
Added special-case handling for instance variables in a dataclass tha…
Browse files Browse the repository at this point in the history
…t are marked `Final`. Previously, these were flagged as an error because there was no explicit value assigned to them, but the synthesized `__init__` method implicitly initializes them.
  • Loading branch information
msfterictraut committed Jan 21, 2022
1 parent 248b8d2 commit a45b8dc
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 1 deletion.
23 changes: 22 additions & 1 deletion packages/pyright-internal/src/analyzer/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2122,7 +2122,28 @@ export class Checker extends ParseTreeWalker {
if (!sawAssignment && !this._fileInfo.isStubFile) {
const firstDecl = decls.find((decl) => decl.type === DeclarationType.Variable && decl.isFinal);
if (firstDecl) {
this._evaluator.addError(Localizer.Diagnostic.finalUnassigned().format({ name }), firstDecl.node);
// Is this an instance variable declared within a dataclass? If so, it
// is implicitly initialized by the synthesized `__init__` method and
// therefore has an implied assignment.
let isImplicitlyAssigned = false;

if (symbol.isClassMember() && !symbol.isClassVar()) {
const containingClass = ParseTreeUtils.getEnclosingClass(firstDecl.node, /* stopAtFunction */ true);
if (containingClass) {
const classType = this._evaluator.getTypeOfClass(containingClass);
if (
classType &&
isClass(classType.decoratedType) &&
ClassType.isDataClass(classType.decoratedType)
) {
isImplicitlyAssigned = true;
}
}
}

if (!isImplicitlyAssigned) {
this._evaluator.addError(Localizer.Diagnostic.finalUnassigned().format({ name }), firstDecl.node);
}
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions packages/pyright-internal/src/tests/samples/final5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This sample tests that instance variables declared as Final within
# a dataclass do not need to have an explicit assignment because
# the generated __init__ method will assign them.

from dataclasses import dataclass
from typing import Final


class Foo1:
x: Final[int]

def __init__(self, x: int) -> None:
self.x = x


@dataclass
class Foo2:
x: Final[int]
5 changes: 5 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ test('Final4', () => {
TestUtils.validateResults(analysisResults, 3);
});

test('Final5', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['final5.py']);
TestUtils.validateResults(analysisResults, 0);
});

test('InferredTypes1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['inferredTypes1.py']);
TestUtils.validateResults(analysisResults, 0);
Expand Down

0 comments on commit a45b8dc

Please sign in to comment.