Skip to content

Commit

Permalink
Added support for a "bare" Final annotation in a dataclass entry th…
Browse files Browse the repository at this point in the history
…at has a default value assigned to it. This addresses #9468.
  • Loading branch information
erictraut committed Nov 30, 2024
1 parent 6a6c9eb commit 789e615
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 3 deletions.
22 changes: 19 additions & 3 deletions packages/pyright-internal/src/analyzer/dataClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,10 @@ export function synthesizeDataClassMethods(
return;
}

let isInferredFinal = false;

// Only variables (not functions, classes, etc.) are considered.
const classVarDecl = symbol.getTypedDeclarations().find((decl) => {
let classVarDecl = symbol.getTypedDeclarations().find((decl) => {
if (decl.type !== DeclarationType.Variable) {
return false;
}
Expand All @@ -195,6 +197,15 @@ export function synthesizeDataClassMethods(
return true;
});

// See if this is an unannotated (inferred) Final value.
if (!classVarDecl) {
classVarDecl = symbol.getDeclarations().find((decl) => {
return decl.type === DeclarationType.Variable && !decl.typeAnnotationNode && decl.isFinal;
});

isInferredFinal = true;
}

if (classVarDecl) {
let statement: ParseNode | undefined = classVarDecl.node;

Expand Down Expand Up @@ -236,15 +247,20 @@ export function synthesizeDataClassMethods(
variableNameNode = statement.d.leftExpr.d.valueExpr;
typeAnnotationNode = statement.d.leftExpr;
const assignmentStatement = statement;
variableTypeEvaluator = () =>
evaluator.getTypeOfAnnotation(
variableTypeEvaluator = () => {
if (isInferredFinal && defaultExpr) {
return evaluator.getTypeOfExpression(defaultExpr).type;
}

return evaluator.getTypeOfAnnotation(
(assignmentStatement.d.leftExpr as TypeAnnotationNode).d.annotation,
{
varTypeAnnotation: true,
allowFinal: true,
allowClassVar: true,
}
);
};
}

hasDefault = true;
Expand Down
17 changes: 17 additions & 0 deletions packages/pyright-internal/src/tests/samples/dataclass18.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This sample tests the case where a "bare" Final is used in a dataclass
# with a default value.

from typing import Final
from dataclasses import dataclass


@dataclass
class DC1:
a: Final = 1


v1 = DC1(1)
reveal_type(v1.a, expected_text="Literal[1]")

v2 = DC1()
reveal_type(v2.a, expected_text="Literal[1]")
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ test('DataClass17', () => {
TestUtils.validateResults(analysisResults, 6);
});

test('DataClass18', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclass18.py']);

TestUtils.validateResults(analysisResults, 0);
});

test('DataClassReplace1', () => {
const configOptions = new ConfigOptions(Uri.empty());

Expand Down

0 comments on commit 789e615

Please sign in to comment.