PY-84469 Consider ClassVar when matching protocol and a concrete class

GitOrigin-RevId: 76af969cfe9318049f02dc7f6bdd03fe20a9504f
This commit is contained in:
evgeny.bovykin
2025-10-21 09:52:41 +00:00
committed by intellij-monorepo-bot
parent 044c140516
commit f9b4104d74
6 changed files with 533 additions and 454 deletions
@@ -38,6 +38,8 @@ public class PyCustomMember extends UserDataHolderBase {
private final PsiElement myTarget;
private PyPsiPath myPsiPath;
private boolean myIsClassVar = false;
boolean myFunction = false;
/**
@@ -243,6 +245,16 @@ public class PyCustomMember extends UserDataHolderBase {
return myFunction;
}
public PyCustomMember asClassVar() {
myIsClassVar = true;
return this;
}
public boolean isClassVar() {
return myIsClassVar;
}
/**
* Checks if some reference points to this element
*
@@ -1101,6 +1101,8 @@ INSP.protocol.newtype.cannot.be.used.with.protocol.classes=NewType cannot be use
INSP.protocol.element.type.incompatible.with.protocol=Type of ''{0}'' is incompatible with ''{1}''
INSP.protocol.cannot.instantiate.protocol.class=Cannot instantiate protocol class ''{0}''
INSP.protocol.element.type.not.writable=''{0}'' is writable in protocol ''{1}''
INSP.protocol.element.type.not.classvar=Cannot override class variable from protocol ''{0}'' with instance variable
INSP.protocol.element.type.not.instancevar=Cannot override instance variable from protocol ''{0}'' with class variable
# PyShadowingBuiltinsInspection
INSP.NAME.shadowing.builtins=Shadowing built-in names
@@ -51,7 +51,7 @@ public final class PyDataclassClassMembersProvider extends PyClassMembersProvide
if (hasAttrs) {
PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(pyClass);
PyClass objectClass = builtinCache.getClass(PyNames.OBJECT);
result.add(new PyCustomMember("__attrs_attrs__", objectClass));
result.add(new PyCustomMember("__attrs_attrs__", objectClass).asClassVar());
}
return CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT);
@@ -17,6 +17,7 @@ import com.jetbrains.python.codeInsight.PyCustomMember;
import com.jetbrains.python.codeInsight.PyCustomMemberUtils;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.*;
import com.jetbrains.python.psi.impl.references.PyReferenceImpl;
@@ -607,7 +608,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
type = resolveContext.getTypeEvalContext().getType(typedElement);
}
result.add(new PyTypeMember(element, type));
boolean isClassVar = false;
if (element instanceof PyAnnotationOwner && element instanceof PyTypeCommentOwner) {
isClassVar =
PyTypingTypeProvider.isClassVar((PyAnnotationOwner & PyTypeCommentOwner)element, resolveContext.getTypeEvalContext());
}
result.add(new PyTypeMember(element, type, isClassVar));
}
}
return true;
@@ -689,12 +695,16 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
return ContainerUtil.map(results, result -> {
PsiElement element = result.getElement();
boolean isClassVar = false;
if (element instanceof PyAnnotationOwner && element instanceof PyTypeCommentOwner) {
isClassVar = PyTypingTypeProvider.isClassVar((PyAnnotationOwner & PyTypeCommentOwner)element, context.getTypeEvalContext());
}
if (element instanceof PyTypedElement typedElement) {
return new PyTypeMember(typedElement,
context.getTypeEvalContext().getType(typedElement), typedElement, typedElement, typedElement);
context.getTypeEvalContext().getType(typedElement), isClassVar);
}
else {
return new PyTypeMember(element, null, element, element, element);
return new PyTypeMember(element, null, isClassVar);
}
});
}
@@ -596,6 +596,12 @@ public final class PyTypeChecker {
if (protocolMember.isDeletable() && !subclassElementMember.isDeletable()) {
return false;
}
boolean isProtocolMemberClassVar = protocolMember.isClassVar();
boolean isSubclassMemberClassVar = subclassElementMember.isClassVar();
if (isSubclassMemberClassVar != isProtocolMemberClassVar) {
return false;
}
PyType subclassElementType = dropSelfIfNeeded(actual, subclassElementMember.getType(), matchContext.context);
subclassElementType = substitute(subclassElementType, substitutions, matchContext.context);
boolean matched = match(protocolElementType, subclassElementType, protocolContext).orElse(true);
File diff suppressed because it is too large Load Diff