PY-84538 Report protocol instantiation as an error

(cherry picked from commit ad941a5d9a5fb852b0c28cbf259fdcc5ce3f3efb)

IJ-MR-173617

GitOrigin-RevId: 97c2b967b5a2c42d0f1c6c5f09a65beb64bd634c
This commit is contained in:
evgeny.bovykin
2025-11-03 16:01:01 +00:00
committed by intellij-monorepo-bot
parent db45df3fd6
commit 96a0d7da31
3 changed files with 91 additions and 11 deletions
@@ -1100,6 +1100,7 @@ INSP.protocol.all.bases.protocol.must.be.protocols=All bases of a protocol must
INSP.protocol.only.runtime.checkable.protocols.can.be.used.with.instance.class.checks=Only @runtime_checkable protocols can be used with instance and class checks
INSP.protocol.newtype.cannot.be.used.with.protocol.classes=NewType cannot be used with protocol classes
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}''
# PyShadowingBuiltinsInspection
INSP.NAME.shadowing.builtins=Shadowing built-in names
@@ -18,10 +18,12 @@ import com.jetbrains.python.psi.types.*
class PyProtocolInspection : PyInspection() {
override fun buildVisitor(holder: ProblemsHolder,
isOnTheFly: Boolean,
session: LocalInspectionToolSession): PsiElementVisitor = Visitor(
holder,PyInspectionVisitor.getContext(session))
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean,
session: LocalInspectionToolSession,
): PsiElementVisitor = Visitor(
holder, PyInspectionVisitor.getContext(session))
private class Visitor(holder: ProblemsHolder, context: TypeEvalContext) : PyInspectionVisitor(holder, context) {
@@ -40,6 +42,7 @@ class PyProtocolInspection : PyInspection() {
checkRuntimeProtocolInIsInstance(node)
checkNewTypeWithProtocols(node)
checkProtocolInstantiation(node)
}
private fun checkCompatibility(type: PyClassType, superClassTypes: List<PyClassLikeType?>) {
@@ -110,21 +113,21 @@ class PyProtocolInspection : PyInspection() {
val resolved = callee.followAssignmentsChain(resolveContext).element ?: return
val isNewTypeCall = resolved is PyQualifiedNameOwner && resolved.qualifiedName == PyTypingTypeProvider.NEW_TYPE
if (isNewTypeCall) {
val base = node.arguments.getOrNull(1)
if (base != null) {
val type = myTypeEvalContext.getType(base)
if (type is PyClassLikeType && isProtocol(type, myTypeEvalContext)) {
registerProblem(base, PyPsiBundle.message("INSP.protocol.newtype.cannot.be.used.with.protocol.classes"))
}
val base = node.arguments.getOrNull(1)
if (base != null) {
val type = myTypeEvalContext.getType(base)
if (type is PyClassLikeType && isProtocol(type, myTypeEvalContext)) {
registerProblem(base, PyPsiBundle.message("INSP.protocol.newtype.cannot.be.used.with.protocol.classes"))
}
}
}
}
private fun checkMemberCompatibility(
protocolElement: PyTypedElement,
subclassElements: List<PyTypedResolveResult>,
type: PyClassType,
protocol: PyClassType
protocol: PyClassType,
) {
val expectedMemberType = myTypeEvalContext.getType(protocolElement)
@@ -139,5 +142,18 @@ class PyProtocolInspection : PyInspection() {
registerProblem(place, PyPsiBundle.message("INSP.protocol.element.type.incompatible.with.protocol", elementName, protocol.name))
}
}
private fun checkProtocolInstantiation(node: PyCallExpression) {
val calleeReferenceExpression = node.callee
if (calleeReferenceExpression is PyReferenceExpression) {
val resolveResult = calleeReferenceExpression.followAssignmentsChain(resolveContext)
val cls = resolveResult.getElement()
if (cls is PyClass) {
if (isProtocol(cls, myTypeEvalContext)) {
registerProblem(node, PyPsiBundle.message("INSP.protocol.cannot.instantiate.protocol.class", cls.name))
}
}
}
}
}
}
@@ -44,6 +44,69 @@ public class PyProtocolInspectionTest extends PyInspectionTestCase {
});
}
// PY-76903
public void testProtocolCannotBeInstantiated() {
doTestByText("""
from typing import Protocol
class P(Protocol):
...
p = <warning descr="Cannot instantiate protocol class 'P'">P()</warning>
""");
}
// PY-76903
public void testProtocolSubclassCanBeInstantiated() {
doTestByText("""
from typing import Protocol
class P(Protocol):
...
class C(P):
...
c = C()
""");
}
// PY-76903
public void testProtocolFunctionCall() {
doTestByText("""
from typing import Protocol
class P(Protocol):
def foo(self) -> None: ...
def f(p: P):
p.foo()
""");
}
// PY-76903
public void testProtocolTypeButConcreteValue() {
doTestByText("""
from typing import Protocol, reveal_type
class P(Protocol):
def foo(self) -> None: ...
class I(P):
def foo(self) -> None: ...
def f() -> type[P]:
return I
t = f()
t()
""");
}
@Override
protected void doTest() {
runWithLanguageLevel(LanguageLevel.PYTHON37, () -> super.doTest());