mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Fixed bug in protocol matching that results in a false positive when the subject object is a dataclass that contains a callable. It should be considered an instance member in this case, so it should not be bound to the class. This addresses #7735. (#7749)
This commit is contained in:
parent
d1461cee9f
commit
cd483dcb90
@ -441,23 +441,40 @@ function assignClassToProtocolInternal(
|
||||
// If the source is a method, bind it.
|
||||
if (isFunction(srcMemberType) || isOverloadedFunction(srcMemberType)) {
|
||||
if (isMemberFromMetaclass || isInstantiableClass(srcMemberInfo.classType)) {
|
||||
const boundSrcFunction = evaluator.bindFunctionToClassOrObject(
|
||||
sourceIsClassObject && !isMemberFromMetaclass
|
||||
? srcType
|
||||
: ClassType.cloneAsInstance(srcType),
|
||||
srcMemberType,
|
||||
isMemberFromMetaclass ? undefined : (srcMemberInfo.classType as ClassType),
|
||||
/* treatConstructorAsClassMethod */ undefined,
|
||||
isMemberFromMetaclass ? srcType : selfType,
|
||||
diag?.createAddendum(),
|
||||
recursionCount
|
||||
);
|
||||
let isInstanceMember = !srcMemberInfo.symbol.isClassMember();
|
||||
|
||||
if (boundSrcFunction) {
|
||||
srcMemberType = removeParamSpecVariadicsFromSignature(boundSrcFunction);
|
||||
} else {
|
||||
typesAreConsistent = false;
|
||||
return;
|
||||
// Special-case dataclasses whose entries act like instance members.
|
||||
if (ClassType.isDataClass(srcType)) {
|
||||
const dataClassFields = ClassType.getDataClassEntries(srcType);
|
||||
if (dataClassFields.some((entry) => entry.name === name)) {
|
||||
isInstanceMember = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMemberFromMetaclass) {
|
||||
isInstanceMember = false;
|
||||
}
|
||||
|
||||
// If this is a callable stored in an instance member, skip binding.
|
||||
if (!isInstanceMember) {
|
||||
const boundSrcFunction = evaluator.bindFunctionToClassOrObject(
|
||||
sourceIsClassObject && !isMemberFromMetaclass
|
||||
? srcType
|
||||
: ClassType.cloneAsInstance(srcType),
|
||||
srcMemberType,
|
||||
isMemberFromMetaclass ? undefined : (srcMemberInfo.classType as ClassType),
|
||||
/* treatConstructorAsClassMethod */ undefined,
|
||||
isMemberFromMetaclass ? srcType : selfType,
|
||||
diag?.createAddendum(),
|
||||
recursionCount
|
||||
);
|
||||
|
||||
if (boundSrcFunction) {
|
||||
srcMemberType = removeParamSpecVariadicsFromSignature(boundSrcFunction);
|
||||
} else {
|
||||
typesAreConsistent = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
packages/pyright-internal/src/tests/samples/protocol49.py
Normal file
21
packages/pyright-internal/src/tests/samples/protocol49.py
Normal file
@ -0,0 +1,21 @@
|
||||
# This sample tests the case where a protocol is matched against a
|
||||
# dataclass. Dataclass fields need to act as if they are instance
|
||||
# members rather than class members, which means a callable stored
|
||||
# in a dataclass member should not be bound to the dataclass itself.
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Protocol
|
||||
|
||||
|
||||
class HasA(Protocol):
|
||||
@property
|
||||
def a(self) -> Callable[[int], int]: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class A:
|
||||
a: Callable[[int], int]
|
||||
|
||||
|
||||
def func1(a: A):
|
||||
has_a: HasA = a
|
@ -1417,6 +1417,12 @@ test('Protocol48', () => {
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('Protocol49', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol49.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('ProtocolExplicit1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocolExplicit1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user