PY-76842 Conformance test failure: aliases_newtype.py

NewType's type argument check.

GitOrigin-RevId: f4c2c6fc61b604458cb6182f1e4db7f8f53f2087
This commit is contained in:
Petr
2024-12-06 18:16:42 +00:00
committed by intellij-monorepo-bot
parent 653a862513
commit 158dc1aeab
4 changed files with 64 additions and 7 deletions
@@ -4,16 +4,23 @@
Reports invalid usages of <a href="https://docs.python.org/3/library/typing.html#typing.NewType">NewType</a>.
</p>
<p>
<b>Example:</b>
<b>Examples:</b>
</p>
<pre><code>
from typing import NewType
from typing import NewType
Base = NewType("Base", str)
A = NewType("B", int) # Variable name 'A' does not match NewType name 'B'
InvalidName = NewType("Name", int) # Variable name 'InvalidName' does not match NewType name 'Name'
</code></pre>
<pre><code>
from typing import Literal
class Derived(Base): # 'Base' cannot be subclassed
pass
InvalidType = NewType("InvalidType", Literal[1]) # NewType cannot be used with 'Literal[1]'
</code></pre>
<pre><code>
Base = NewType("Base", str)
class Derived(Base): # 'Base' cannot be subclassed
pass
</code></pre>
</body>
</html>
@@ -1249,4 +1249,7 @@ INSP.enum.type.annotations.are.not.allowed.for.enum.members=Type annotations are
#PyNewTypeInspection
INSP.NAME.new.type=Invalid usage of NewType
INSP.NAME.new.type.cannot.be.subclassed=''{0}'' cannot be subclassed
INSP.NAME.new.type.variable.name.does.not.match.new.type.name=Variable name ''{0}'' does not match NewType name ''{1}''
INSP.NAME.new.type.variable.name.does.not.match.new.type.name=Variable name ''{0}'' does not match NewType name ''{1}''
INSP.NAME.new.type.expected.class=Expected class
INSP.NAME.new.type.new.type.cannot.be.used.with=NewType cannot be used with ''{0}''
INSP.NAME.new.type.new.type.cannot.be.used.with.protocol.classes=NewType cannot be used with protocol classes
@@ -3,13 +3,20 @@ package com.jetbrains.python.inspections
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElementVisitor
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.codeInsight.typing.isProtocol
import com.jetbrains.python.psi.PyCallExpression
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyExpression
import com.jetbrains.python.psi.PyTargetExpression
import com.jetbrains.python.psi.impl.PyPsiUtils
import com.jetbrains.python.psi.resolve.PyResolveUtil
import com.jetbrains.python.psi.types.PyClassType
import com.jetbrains.python.psi.types.PyLiteralType
import com.jetbrains.python.psi.types.PyTypedDictType
import com.jetbrains.python.psi.types.PyTypingNewType
class PyNewTypeInspection : PyInspection() {
@@ -30,6 +37,23 @@ class PyNewTypeInspection : PyInspection() {
registerProblem(node.nameIdentifier,
PyPsiBundle.message("INSP.NAME.new.type.variable.name.does.not.match.new.type.name", targetName, newTypeName))
}
val typeExpr = PyPsiUtils.flattenParens(assignedValue.getArgument(1, "tp", PyExpression::class.java))
if (typeExpr != null) {
val type = Ref.deref(PyTypingTypeProvider.getType(typeExpr, myTypeEvalContext))
if (type !is PyClassType) {
registerProblem(typeExpr, PyPsiBundle.message("INSP.NAME.new.type.expected.class"))
}
else if (type is PyLiteralType) {
registerProblem(typeExpr, PyPsiBundle.message("INSP.NAME.new.type.new.type.cannot.be.used.with", type.name))
}
else if (type is PyTypedDictType) {
registerProblem(typeExpr, PyPsiBundle.message("INSP.NAME.new.type.new.type.cannot.be.used.with", "TypedDict"))
}
else if (isProtocol(type, myTypeEvalContext)) {
registerProblem(typeExpr, PyPsiBundle.message("INSP.NAME.new.type.new.type.cannot.be.used.with.protocol.classes"))
}
}
}
}
@@ -33,5 +33,28 @@ class PyNewTypeInspectionTest : PyInspectionTestCase() {
)
}
fun testType() {
doTestByText(
"""
from typing import NewType, Literal, TypedDict, Protocol
class TD(TypedDict):
name: str
class MyProtocol(Protocol):
pass
NewType1 = NewType(tp=int, name="NewType1")
NewType2 = NewType("NewType2", NewType1)
NewType3 = NewType("NewType3", <warning descr="Expected class">int | str</warning>)
NewType4 = NewType("NewType4", <warning descr="Expected class">1</warning>)
NewType5 = NewType("NewType5", <warning descr="NewType cannot be used with 'Literal[1]'">Literal[1]</warning>)
NewType6 = NewType("NewType6", <warning descr="NewType cannot be used with 'TypedDict'">TD</warning>)
NewType7 = NewType("NewType7", <warning descr="NewType cannot be used with protocol classes">MyProtocol</warning>)
NewType8 = NewType("NewType8", list[int])
""".trimIndent()
)
}
override fun getInspectionClass(): Class<out PyInspection> = PyNewTypeInspection::class.java
}