Skip to content

Commit f54ca1a

Browse files
authored
Added check for invalid use of ClassVar qualifier within a NamedTuple or TypedDict attribute annotation. This addresses #9526. (#9540)
1 parent b0cd5f1 commit f54ca1a

File tree

5 files changed

+43
-16
lines changed

5 files changed

+43
-16
lines changed

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,15 +1178,6 @@ export function isFinalAllowedForAssignmentTarget(targetNode: ExpressionNode): b
11781178
return false;
11791179
}
11801180

1181-
export function isClassVarAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
1182-
const classNode = getEnclosingClass(targetNode, /* stopAtFunction */ true);
1183-
if (!classNode) {
1184-
return false;
1185-
}
1186-
1187-
return true;
1188-
}
1189-
11901181
export function isRequiredAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
11911182
const classNode = getEnclosingClass(targetNode, /* stopAtFunction */ true);
11921183
if (!classNode) {

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4360,7 +4360,7 @@ export function createTypeEvaluator(
43604360
let annotationType: Type | undefined = getTypeOfAnnotation(target.d.annotation, {
43614361
varTypeAnnotation: true,
43624362
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(target.d.valueExpr),
4363-
allowClassVar: ParseTreeUtils.isClassVarAllowedForAssignmentTarget(target.d.valueExpr),
4363+
allowClassVar: isClassVarAllowedForAssignmentTarget(target.d.valueExpr),
43644364
});
43654365

43664366
if (annotationType) {
@@ -4433,6 +4433,21 @@ export function createTypeEvaluator(
44334433
}
44344434
}
44354435

4436+
function isClassVarAllowedForAssignmentTarget(targetNode: ExpressionNode): boolean {
4437+
const classNode = ParseTreeUtils.getEnclosingClass(targetNode, /* stopAtFunction */ true);
4438+
if (!classNode) {
4439+
return false;
4440+
}
4441+
4442+
// ClassVar is not allowed in a TypedDict or a NamedTuple class.
4443+
const classType = getTypeOfClass(classNode)?.classType;
4444+
if (!classType) {
4445+
return false;
4446+
}
4447+
4448+
return !ClassType.isTypedDictClass(classType) && !classType.shared.namedTupleEntries;
4449+
}
4450+
44364451
function verifyRaiseExceptionType(node: ExpressionNode, allowNone: boolean) {
44374452
const baseExceptionType = getBuiltInType(node, 'BaseException');
44384453
const exceptionType = getTypeOfExpression(node).type;
@@ -19924,7 +19939,7 @@ export function createTypeEvaluator(
1992419939
const annotationType = getTypeOfAnnotation(node.d.annotation, {
1992519940
varTypeAnnotation: true,
1992619941
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(node.d.valueExpr),
19927-
allowClassVar: ParseTreeUtils.isClassVarAllowedForAssignmentTarget(node.d.valueExpr),
19942+
allowClassVar: isClassVarAllowedForAssignmentTarget(node.d.valueExpr),
1992819943
});
1992919944

1993019945
writeTypeCache(node.d.valueExpr, { type: annotationType }, EvalFlags.None);
@@ -20073,7 +20088,7 @@ export function createTypeEvaluator(
2007320088
getTypeOfAnnotation(annotationNode, {
2007420089
varTypeAnnotation: true,
2007520090
allowFinal: ParseTreeUtils.isFinalAllowedForAssignmentTarget(annotationParent.d.leftExpr),
20076-
allowClassVar: ParseTreeUtils.isClassVarAllowedForAssignmentTarget(annotationParent.d.leftExpr),
20091+
allowClassVar: isClassVarAllowedForAssignmentTarget(annotationParent.d.leftExpr),
2007720092
});
2007820093
} else {
2007920094
evaluateTypesForAssignmentStatement(annotationParent);
@@ -22048,7 +22063,7 @@ export function createTypeEvaluator(
2204822063
declaration.node.parent?.nodeType === ParseNodeType.MemberAccess
2204922064
? declaration.node.parent
2205022065
: declaration.node;
22051-
const allowClassVar = ParseTreeUtils.isClassVarAllowedForAssignmentTarget(declNode);
22066+
const allowClassVar = isClassVarAllowedForAssignmentTarget(declNode);
2205222067
const allowFinal = ParseTreeUtils.isFinalAllowedForAssignmentTarget(declNode);
2205322068
const allowRequired =
2205422069
ParseTreeUtils.isRequiredAllowedForAssignmentTarget(declNode) ||
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# This sample tests that a ClassVar is disallowed when used in a
2+
# NamedTuple or TypedDict class as reflected in the runtime.
3+
4+
from typing import ClassVar, NamedTuple, TypedDict
5+
6+
class NT1(NamedTuple):
7+
# This should generate an error.
8+
x: ClassVar
9+
10+
# This should generate an error.
11+
y: ClassVar[int]
12+
13+
class TD1(TypedDict):
14+
# This should generate an error.
15+
x: ClassVar
16+
17+
# This should generate an error.
18+
y: ClassVar[int]

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@ class DataTuple(NamedTuple):
1515
def _m(self):
1616
pass
1717

18-
# ClassVar variables should not be included.
19-
class_var: ClassVar[int] = 4
20-
2118
id: int
2219
aid: Other
2320
value: str = ""

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,12 @@ test('ClassVar5', () => {
805805
TestUtils.validateResults(analysisResults, 0);
806806
});
807807

808+
test('ClassVar6', () => {
809+
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['classVar6.py']);
810+
811+
TestUtils.validateResults(analysisResults, 4);
812+
});
813+
808814
test('TypeVar1', () => {
809815
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeVar1.py']);
810816

0 commit comments

Comments
 (0)