diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index 558d920217d0..04dc1483bee2 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -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 diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyProtocolInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyProtocolInspection.kt index d5db6fd8c558..f78c3b5077a4 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyProtocolInspection.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyProtocolInspection.kt @@ -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) { @@ -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, 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)) + } + } + } + } } } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyProtocolInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyProtocolInspectionTest.java index c15fd8b7efc5..f8e68f2710d0 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyProtocolInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyProtocolInspectionTest.java @@ -44,6 +44,69 @@ public class PyProtocolInspectionTest extends PyInspectionTestCase { }); } + // PY-76903 + public void testProtocolCannotBeInstantiated() { + doTestByText(""" + from typing import Protocol + + class P(Protocol): + ... + + p = P() + """); + } + + // 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());