Skip to content

Commit e6d30a9

Browse files
authored
Added missing check for illegal use of Final within a TypedDict or NamedTuple definition. This addresses #9767. (#9773)
1 parent 3b0f028 commit e6d30a9

File tree

4 files changed

+42
-12
lines changed

4 files changed

+42
-12
lines changed

packages/pyright-internal/src/analyzer/dataClasses.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,8 @@ export function synthesizeDataClassMethods(
259259
(assignmentStatement.d.leftExpr as TypeAnnotationNode).d.annotation,
260260
{
261261
varTypeAnnotation: true,
262-
allowFinal: true,
263-
allowClassVar: true,
262+
allowFinal: !isNamedTuple,
263+
allowClassVar: !isNamedTuple,
264264
}
265265
);
266266
};
@@ -370,8 +370,8 @@ export function synthesizeDataClassMethods(
370370
variableTypeEvaluator = () =>
371371
evaluator.getTypeOfAnnotation(annotationStatement.d.annotation, {
372372
varTypeAnnotation: true,
373-
allowFinal: true,
374-
allowClassVar: true,
373+
allowFinal: !isNamedTuple,
374+
allowClassVar: !isNamedTuple,
375375
});
376376

377377
// Is this a KW_ONLY separator introduced in Python 3.10?

packages/pyright-internal/src/analyzer/typeEvaluator.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4392,7 +4392,7 @@ export function createTypeEvaluator(
43924392
case ParseNodeType.TypeAnnotation: {
43934393
let annotationType: Type | undefined = getTypeOfAnnotation(target.d.annotation, {
43944394
varTypeAnnotation: true,
4395-
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(target.d.valueExpr),
4395+
allowFinal: isFinalAllowedForAssignmentTarget(target.d.valueExpr),
43964396
allowClassVar: isClassVarAllowedForAssignmentTarget(target.d.valueExpr),
43974397
});
43984398

@@ -4467,18 +4467,34 @@ export function createTypeEvaluator(
44674467
}
44684468

44694469
function isClassVarAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
4470+
// ClassVar is allowed only in a class body.
44704471
const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true);
44714472
if (!classNode) {
44724473
return false;
44734474
}
44744475

44754476
// ClassVar is not allowed in a TypedDict or a NamedTuple class.
4477+
return !isInTypedDictOrNamedTuple(classNode);
4478+
}
4479+
4480+
function isFinalAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
4481+
const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true);
4482+
4483+
// Final is not allowed in the body of a TypedDict or NamedTuple class.
4484+
if (classNode && isInTypedDictOrNamedTuple(classNode)) {
4485+
return false;
4486+
}
4487+
4488+
return ParseTreeUtils.isFinalAllowedForAssignmentTarget(targetNode);
4489+
}
4490+
4491+
function isInTypedDictOrNamedTuple(classNode: ClassNode): boolean {
44764492
const classType = getTypeOfClass(classNode)?.classType;
44774493
if (!classType) {
44784494
return false;
44794495
}
44804496

4481-
return !ClassType.isTypedDictClass(classType) && !classType.shared.namedTupleEntries;
4497+
return ClassType.isTypedDictClass(classType) || !!classType.shared.namedTupleEntries;
44824498
}
44834499

44844500
function verifyRaiseExceptionType(node: ExpressionNode, allowNone: boolean) {
@@ -20038,7 +20054,7 @@ export function createTypeEvaluator(
2003820054
} else {
2003920055
const annotationType = getTypeOfAnnotation(node.d.annotation, {
2004020056
varTypeAnnotation: true,
20041-
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(node.d.valueExpr),
20057+
allowFinal: isFinalAllowedForAssignmentTarget(node.d.valueExpr),
2004220058
allowClassVar: isClassVarAllowedForAssignmentTarget(node.d.valueExpr),
2004320059
});
2004420060

@@ -20187,7 +20203,7 @@ export function createTypeEvaluator(
2018720203
if (annotationNode === annotationParent.d.annotationComment) {
2018820204
getTypeOfAnnotation(annotationNode, {
2018920205
varTypeAnnotation: true,
20190-
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr),
20206+
allowFinal: isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr),
2019120207
allowClassVar: isClassVarAllowedForAssignmentTarget(annotationParent.d.leftExpr),
2019220208
});
2019320209
} else {
@@ -22169,7 +22185,7 @@ export function createTypeEvaluator(
2216922185
? declaration.node.parent
2217022186
: declaration.node;
2217122187
const allowClassVar = isClassVarAllowedForAssignmentTarget(declNode);
22172-
const allowFinal = ParseTreeUtils.isFinalAllowedForAssignmentTarget(declNode);
22188+
const allowFinal = isFinalAllowedForAssignmentTarget(declNode);
2217322189
const allowRequired =
2217422190
ParseTreeUtils.isRequiredAllowedForAssignmentTarget(declNode) ||
2217522191
!!declaration.isInInlinedTypedDict;

packages/pyright-internal/src/tests/samples/dataclassNamedTuple1.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ def _m(self):
2323
not_annotated = 5
2424

2525
name: str | None = None
26-
27-
name2: Final[str | None] = None
26+
name2: str | None = None
2827

2928
not_a_method = standalone
3029

@@ -47,3 +46,18 @@ def _m(self):
4746
# This should generate an error because aid is a required
4847
# parameter and is missing an argument here.
4948
d6 = DataTuple(id=1, name=None)
49+
50+
51+
class DataTuple2(NamedTuple):
52+
# This should generate an error because Final cannot
53+
# be used in a NamedTuple. A second downstream error
54+
# is also generated.
55+
x: Final[int]
56+
57+
# This should generate an error because Final cannot
58+
# be used in a NamedTuple.
59+
y: Final = 1
60+
61+
# This should generate an error because ClassVar cannot
62+
# be used in a NamedTuple.
63+
z: ClassVar[int] = 1

packages/pyright-internal/src/tests/typeEvaluator4.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ test('MemberAccess28', () => {
265265
test('DataClassNamedTuple1', () => {
266266
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassNamedTuple1.py']);
267267

268-
TestUtils.validateResults(analysisResults, 2);
268+
TestUtils.validateResults(analysisResults, 6);
269269
});
270270

271271
test('DataClassNamedTuple2', () => {

0 commit comments

Comments
 (0)