mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 21:41:24 +07:00
PY-75655 False negative when updating ReadOnly TypedDict field using update() method
GitOrigin-RevId: 42428a0541893481046952407fce2d99a02081fa
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6a08ff233d
commit
08ad3f7789
@@ -191,12 +191,20 @@ class PyTypedDictInspection : PyInspection() {
|
||||
|
||||
val nodeType = myTypeEvalContext.getType(callee.qualifier!!)
|
||||
if (nodeType !is PyTypedDictType || nodeType.isInferred()) return
|
||||
val arguments = node.arguments
|
||||
var arguments = node.arguments
|
||||
|
||||
if (PyNames.UPDATE == callee.name) {
|
||||
inspectUpdateSequenceArgument(
|
||||
if (arguments.size == 1 && arguments[0] is PySequenceExpression) (arguments[0] as PySequenceExpression).elements else arguments,
|
||||
nodeType)
|
||||
if (arguments.size == 1 && arguments[0] is PyReferenceExpression) {
|
||||
(PyUtil.resolveToTheTop(arguments[0]) as? PyTargetExpression)?.let { resolvedArg ->
|
||||
resolvedArg.findAssignedValue()?.let {
|
||||
arguments = arrayOf<PyExpression>(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arguments.size == 1 && arguments[0] is PySequenceExpression) {
|
||||
arguments = (arguments[0] as PySequenceExpression).elements
|
||||
}
|
||||
inspectUpdateSequenceArgument(node, arguments, nodeType)
|
||||
}
|
||||
|
||||
if (PyNames.CLEAR == callee.name || PyNames.POPITEM == callee.name) {
|
||||
@@ -390,7 +398,7 @@ class PyTypedDictInspection : PyInspection() {
|
||||
PyTypeChecker.match(expected.type, actual.type, myTypeEvalContext)
|
||||
}
|
||||
|
||||
private fun inspectUpdateSequenceArgument(sequenceElements: Array<PyExpression>, typedDictType: PyTypedDictType) {
|
||||
private fun inspectUpdateSequenceArgument(updateCall: PyCallExpression, sequenceElements: Array<PyExpression>, typedDictType: PyTypedDictType) {
|
||||
sequenceElements.forEach {
|
||||
var key: PsiElement? = null
|
||||
var keyAsString: String? = null
|
||||
@@ -430,6 +438,10 @@ class PyTypedDictInspection : PyInspection() {
|
||||
registerProblem(key, PyPsiBundle.message("INSP.typeddict.typeddict.cannot.have.key", typedDictType.name, keyAsString))
|
||||
return@forEach
|
||||
}
|
||||
if (fields.get(keyAsString)!!.qualifiers.isReadOnly) {
|
||||
val warningHolder = (updateCall.callee as? PyReferenceExpression)?.nameElement?.psi ?: updateCall
|
||||
registerProblem(warningHolder, PyPsiBundle.message("INSP.typeddict.typeddict.field.is.readonly", keyAsString))
|
||||
}
|
||||
val valueType = myTypeEvalContext.getType(value)
|
||||
if (!PyTypeChecker.match(fields[keyAsString]?.type, valueType, myTypeEvalContext)) {
|
||||
val expectedTypeName = PythonDocumentationProvider.getTypeName(fields[keyAsString]!!.type, myTypeEvalContext)
|
||||
|
||||
@@ -451,6 +451,23 @@ public class PyTypedDictInspectionTest extends PyInspectionTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public void testUpdateReadOnlyMember() {
|
||||
doTestByText(
|
||||
"""
|
||||
from typing import TypedDict, NotRequired
|
||||
from typing_extensions import ReadOnly
|
||||
|
||||
class A(TypedDict):
|
||||
x: NotRequired[ReadOnly[int]]
|
||||
y: int
|
||||
|
||||
a1: A = { "x": 1, "y": 1 }
|
||||
a2: A = { "x": 2, "y": 4 }
|
||||
a1.<warning descr="TypedDict key \\"x\\" is ReadOnly">update</warning>(a2)
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user