mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Added support for "naked" ClassVar
used to annotate a class variable that is assigned a descriptor object but is later set through an object member access. This previously resulted in a false positive error. This addresses https://github.com/microsoft/pyright/issues/4838.
This commit is contained in:
parent
f63069227c
commit
8a93373e6b
@ -264,6 +264,7 @@ import {
|
||||
getTypeVarScopeId,
|
||||
getUnionSubtypeCount,
|
||||
InferenceContext,
|
||||
isDescriptorInstance,
|
||||
isEffectivelyInstantiable,
|
||||
isEllipsisType,
|
||||
isIncompleteUnknown,
|
||||
@ -5444,10 +5445,30 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
isInstantiableClass(containingClassType) &&
|
||||
ClassType.isSameGenericClass(containingClassType, classType)
|
||||
) {
|
||||
type = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type ?? UnknownType.create();
|
||||
type = getDeclaredTypeOfSymbol(memberInfo.symbol)?.type;
|
||||
if (type && isInstantiableClass(memberInfo.classType)) {
|
||||
type = partiallySpecializeType(type, memberInfo.classType);
|
||||
}
|
||||
|
||||
// If we're setting a class variable via a write through an object,
|
||||
// this is normally considered a type violation. But it is allowed
|
||||
// if the class variable is a descriptor object. In this case, we will
|
||||
// clear the flag that causes an error to be generated.
|
||||
if (usage.method === 'set' && memberInfo.symbol.isClassVar() && isAccessedThroughObject) {
|
||||
const selfClass = bindToType || memberName === '__new__' ? undefined : classType;
|
||||
const typeResult = getTypeOfMemberInternal(memberInfo, selfClass);
|
||||
|
||||
if (typeResult) {
|
||||
if (isDescriptorInstance(typeResult.type, /* requireSetter */ true)) {
|
||||
type = typeResult.type;
|
||||
flags &= MemberAccessFlags.DisallowClassVarWrites;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
type = UnknownType.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21283,6 +21304,13 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
considerDecl = false;
|
||||
}
|
||||
|
||||
// If the symbol is explicitly marked as a ClassVar, consider only the
|
||||
// declarations that assign to it from within the class body, not through
|
||||
// a member access expression.
|
||||
if (symbol.isClassVar() && decl.type === DeclarationType.Variable && decl.isDefinedByMemberAccess) {
|
||||
considerDecl = false;
|
||||
}
|
||||
|
||||
if (usageNode !== undefined) {
|
||||
if (decl.type !== DeclarationType.Alias) {
|
||||
// Is the declaration in the same execution scope as the "usageNode" node?
|
||||
|
@ -0,0 +1,40 @@
|
||||
# This sample tests the case where a member access is performed through
|
||||
# an object using a field that is annotated as a ClassVar. Normally this
|
||||
# is disallowed, but it is permitted if the type of the ClassVar is
|
||||
# a descriptor object.
|
||||
|
||||
from typing import ClassVar, Generic, TypeVar, overload, Self
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Descriptor(Generic[T]):
|
||||
@overload
|
||||
def __get__(self, instance: None, owner) -> Self:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: object, owner) -> T:
|
||||
...
|
||||
|
||||
def __get__(self, instance: object | None, owner) -> Self | T:
|
||||
...
|
||||
|
||||
def __set__(self, instance: object, value: T) -> None:
|
||||
...
|
||||
|
||||
def is_null(self) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class Example:
|
||||
field1: ClassVar = Descriptor[str]()
|
||||
|
||||
field2: ClassVar = ""
|
||||
|
||||
def reset(self) -> None:
|
||||
self.field1 = ""
|
||||
|
||||
# This should generate an error because field2 isn't
|
||||
# a descriptor object.
|
||||
self.field2 = ""
|
@ -513,6 +513,11 @@ test('MemberAccess20', () => {
|
||||
TestUtils.validateResults(analysisResults, 1);
|
||||
});
|
||||
|
||||
test('MemberAccess21', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['memberAccess21.py']);
|
||||
TestUtils.validateResults(analysisResults, 1);
|
||||
});
|
||||
|
||||
test('DataClass1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclass1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user