Skip to content

Commit

Permalink
Added missing check for illegal use of Final within a TypedDict or …
Browse files Browse the repository at this point in the history
…NamedTuple definition. This addresses #9767. (#9773)
  • Loading branch information
erictraut authored Jan 28, 2025
1 parent 3b0f028 commit e6d30a9
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 12 deletions.
8 changes: 4 additions & 4 deletions packages/pyright-internal/src/analyzer/dataClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ export function synthesizeDataClassMethods(
(assignmentStatement.d.leftExpr as TypeAnnotationNode).d.annotation,
{
varTypeAnnotation: true,
allowFinal: true,
allowClassVar: true,
allowFinal: !isNamedTuple,
allowClassVar: !isNamedTuple,
}
);
};
Expand Down Expand Up @@ -370,8 +370,8 @@ export function synthesizeDataClassMethods(
variableTypeEvaluator = () =>
evaluator.getTypeOfAnnotation(annotationStatement.d.annotation, {
varTypeAnnotation: true,
allowFinal: true,
allowClassVar: true,
allowFinal: !isNamedTuple,
allowClassVar: !isNamedTuple,
});

// Is this a KW_ONLY separator introduced in Python 3.10?
Expand Down
26 changes: 21 additions & 5 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4392,7 +4392,7 @@ export function createTypeEvaluator(
case ParseNodeType.TypeAnnotation: {
let annotationType: Type | undefined = getTypeOfAnnotation(target.d.annotation, {
varTypeAnnotation: true,
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(target.d.valueExpr),
allowFinal: isFinalAllowedForAssignmentTarget(target.d.valueExpr),
allowClassVar: isClassVarAllowedForAssignmentTarget(target.d.valueExpr),
});

Expand Down Expand Up @@ -4467,18 +4467,34 @@ export function createTypeEvaluator(
}

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

// ClassVar is not allowed in a TypedDict or a NamedTuple class.
return !isInTypedDictOrNamedTuple(classNode);
}

function isFinalAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true);

// Final is not allowed in the body of a TypedDict or NamedTuple class.
if (classNode && isInTypedDictOrNamedTuple(classNode)) {
return false;
}

return ParseTreeUtils.isFinalAllowedForAssignmentTarget(targetNode);
}

function isInTypedDictOrNamedTuple(classNode: ClassNode): boolean {
const classType = getTypeOfClass(classNode)?.classType;
if (!classType) {
return false;
}

return !ClassType.isTypedDictClass(classType) && !classType.shared.namedTupleEntries;
return ClassType.isTypedDictClass(classType) || !!classType.shared.namedTupleEntries;
}

function verifyRaiseExceptionType(node: ExpressionNode, allowNone: boolean) {
Expand Down Expand Up @@ -20038,7 +20054,7 @@ export function createTypeEvaluator(
} else {
const annotationType = getTypeOfAnnotation(node.d.annotation, {
varTypeAnnotation: true,
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(node.d.valueExpr),
allowFinal: isFinalAllowedForAssignmentTarget(node.d.valueExpr),
allowClassVar: isClassVarAllowedForAssignmentTarget(node.d.valueExpr),
});

Expand Down Expand Up @@ -20187,7 +20203,7 @@ export function createTypeEvaluator(
if (annotationNode === annotationParent.d.annotationComment) {
getTypeOfAnnotation(annotationNode, {
varTypeAnnotation: true,
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr),
allowFinal: isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr),
allowClassVar: isClassVarAllowedForAssignmentTarget(annotationParent.d.leftExpr),
});
} else {
Expand Down Expand Up @@ -22169,7 +22185,7 @@ export function createTypeEvaluator(
? declaration.node.parent
: declaration.node;
const allowClassVar = isClassVarAllowedForAssignmentTarget(declNode);
const allowFinal = ParseTreeUtils.isFinalAllowedForAssignmentTarget(declNode);
const allowFinal = isFinalAllowedForAssignmentTarget(declNode);
const allowRequired =
ParseTreeUtils.isRequiredAllowedForAssignmentTarget(declNode) ||
!!declaration.isInInlinedTypedDict;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ def _m(self):
not_annotated = 5

name: str | None = None

name2: Final[str | None] = None
name2: str | None = None

not_a_method = standalone

Expand All @@ -47,3 +46,18 @@ def _m(self):
# This should generate an error because aid is a required
# parameter and is missing an argument here.
d6 = DataTuple(id=1, name=None)


class DataTuple2(NamedTuple):
# This should generate an error because Final cannot
# be used in a NamedTuple. A second downstream error
# is also generated.
x: Final[int]

# This should generate an error because Final cannot
# be used in a NamedTuple.
y: Final = 1

# This should generate an error because ClassVar cannot
# be used in a NamedTuple.
z: ClassVar[int] = 1
2 changes: 1 addition & 1 deletion packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ test('MemberAccess28', () => {
test('DataClassNamedTuple1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassNamedTuple1.py']);

TestUtils.validateResults(analysisResults, 2);
TestUtils.validateResults(analysisResults, 6);
});

test('DataClassNamedTuple2', () => {
Expand Down

0 comments on commit e6d30a9

Please sign in to comment.