diff --git a/packages/pyright-internal/src/analyzer/dataClasses.ts b/packages/pyright-internal/src/analyzer/dataClasses.ts index 5b694e23f9b2..b56aa9773732 100644 --- a/packages/pyright-internal/src/analyzer/dataClasses.ts +++ b/packages/pyright-internal/src/analyzer/dataClasses.ts @@ -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; } @@ -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; @@ -236,8 +247,12 @@ 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, @@ -245,6 +260,7 @@ export function synthesizeDataClassMethods( allowClassVar: true, } ); + }; } hasDefault = true; diff --git a/packages/pyright-internal/src/tests/samples/dataclass18.py b/packages/pyright-internal/src/tests/samples/dataclass18.py new file mode 100644 index 000000000000..20771e585105 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/dataclass18.py @@ -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]") diff --git a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts index e44714228a50..d2d0c897dbfc 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts @@ -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());