mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-20 20:57:37 +03:00
Fixed bug in protocol matching logic to reject a protocol match if a variable is a ClassVar
in the protocol but not in the concrete class (or vice versa). This addresses #6869. (#6920)
This commit is contained in:
parent
818b423eb0
commit
4b6d636206
@ -586,6 +586,26 @@ function assignClassToProtocolInternal(
|
||||
typesAreConsistent = false;
|
||||
}
|
||||
|
||||
const isDestClassVar = destSymbol.isClassVar();
|
||||
const isSrcClassVar = srcSymbol.isClassVar();
|
||||
|
||||
// If the source is marked as a ClassVar but the dest (the protocol) is not,
|
||||
// or vice versa, the types are not consistent.
|
||||
if (isDestClassVar !== isSrcClassVar) {
|
||||
if (isDestClassVar) {
|
||||
if (subDiag) {
|
||||
subDiag.addMessage(Localizer.DiagnosticAddendum.memberIsClassVarInProtocol().format({ name }));
|
||||
}
|
||||
} else {
|
||||
if (subDiag) {
|
||||
subDiag.addMessage(
|
||||
Localizer.DiagnosticAddendum.memberIsNotClassVarInProtocol().format({ name })
|
||||
);
|
||||
}
|
||||
}
|
||||
typesAreConsistent = false;
|
||||
}
|
||||
|
||||
const destPrimaryDecl = getLastTypedDeclaredForSymbol(destSymbol);
|
||||
const srcPrimaryDecl = getLastTypedDeclaredForSymbol(srcSymbol);
|
||||
|
||||
|
@ -1268,12 +1268,16 @@ export namespace Localizer {
|
||||
);
|
||||
export const memberIsAbstractMore = () =>
|
||||
new ParameterizedString<{ count: number }>(getRawString('DiagnosticAddendum.memberIsAbstractMore'));
|
||||
export const memberIsClassVarInProtocol = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('DiagnosticAddendum.memberIsClassVarInProtocol'));
|
||||
export const memberIsFinalInProtocol = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('DiagnosticAddendum.memberIsFinalInProtocol'));
|
||||
export const memberIsInitVar = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('DiagnosticAddendum.memberIsInitVar'));
|
||||
export const memberIsInvariant = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('DiagnosticAddendum.memberIsInvariant'));
|
||||
export const memberIsNotClassVarInProtocol = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('DiagnosticAddendum.memberIsNotClassVarInProtocol'));
|
||||
export const memberIsNotFinalInProtocol = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('DiagnosticAddendum.memberIsNotFinalInProtocol'));
|
||||
export const memberIsWritableInProtocol = () =>
|
||||
|
@ -650,9 +650,11 @@
|
||||
"memberAssignment": "Expression of type \"{type}\" cannot be assigned to member \"{name}\" of class \"{classType}\"",
|
||||
"memberIsAbstract": "\"{type}.{name}\" is abstract",
|
||||
"memberIsAbstractMore": "and {count} more...",
|
||||
"memberIsClassVarInProtocol": "\"{name}\" is defined as a ClassVar in protocol",
|
||||
"memberIsFinalInProtocol": "\"{name}\" is marked Final in protocol",
|
||||
"memberIsInitVar": "Member \"{name}\" is an init-only field",
|
||||
"memberIsInvariant": "\"{name}\" is invariant because it is mutable",
|
||||
"memberIsNotClassVarInProtocol": "\"{name}\" is not defined as a ClassVar in protocol",
|
||||
"memberIsNotFinalInProtocol": "\"{name}\" is not marked Final in protocol",
|
||||
"memberIsWritableInProtocol": "\"{name}\" is writable in protocol",
|
||||
"memberTypeMismatch": "\"{name}\" is an incompatible type",
|
||||
|
@ -3,13 +3,13 @@
|
||||
# synthesized data classes.
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol
|
||||
from typing import Any, ClassVar, Protocol
|
||||
|
||||
|
||||
class IsDataclass(Protocol):
|
||||
# Checking for this attribute seems to currently be
|
||||
# the most reliable way to ascertain that something is a dataclass
|
||||
__dataclass_fields__: dict[str, Any]
|
||||
__dataclass_fields__: ClassVar[dict[str, Any]]
|
||||
|
||||
|
||||
def dataclass_only(x: IsDataclass):
|
||||
|
@ -1,7 +1,7 @@
|
||||
# This sample tests the assignment of protocols that
|
||||
# include property declarations.
|
||||
|
||||
from typing import ContextManager, NamedTuple, Protocol, TypeVar
|
||||
from typing import ClassVar, ContextManager, NamedTuple, Protocol, Sequence, TypeVar
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@ -208,3 +208,35 @@ p10_1: Proto10 = NT9()
|
||||
p10_2: Proto10 = DCFrozen9()
|
||||
|
||||
p10_3: Proto10 = DC9()
|
||||
|
||||
|
||||
class Proto11(Protocol):
|
||||
val1: ClassVar[Sequence[int]]
|
||||
|
||||
|
||||
class Concrete11:
|
||||
val1: Sequence[int]
|
||||
|
||||
|
||||
# This should generate an error because of a ClassVar mismatch.
|
||||
p11_1: Protoll = Concrete11()
|
||||
|
||||
|
||||
class Proto12(Protocol):
|
||||
val1: Sequence[int]
|
||||
|
||||
|
||||
class Concrete12:
|
||||
val1: ClassVar[Sequence[int]]
|
||||
|
||||
|
||||
# This should generate an error because of a ClassVar mismatch.
|
||||
p12_1: Protol2 = Concrete12()
|
||||
|
||||
|
||||
def func12(p11: Proto11, p12: Proto12):
|
||||
# This should generate an error because of a ClassVar mismatch.
|
||||
v1: Proto12 = p11
|
||||
|
||||
# This should generate an error because of a ClassVar mismatch.
|
||||
v2: Proto11 = p12
|
||||
|
@ -30,6 +30,8 @@ class B:
|
||||
x: int
|
||||
|
||||
|
||||
# This should generate an error because x is not a ClassVar in B
|
||||
# but is a ClassVar in the protocol.
|
||||
b: ProtoB = B()
|
||||
|
||||
|
||||
|
@ -1116,13 +1116,13 @@ test('Protocol2', () => {
|
||||
test('Protocol3', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol3.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 8);
|
||||
TestUtils.validateResults(analysisResults, 12);
|
||||
});
|
||||
|
||||
test('Protocol4', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol4.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 3);
|
||||
TestUtils.validateResults(analysisResults, 4);
|
||||
});
|
||||
|
||||
test('Protocol5', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user