IJ-MR-146029 PY-76149 simplify the check for expected type from __set__ and assigned value

Reword the inspection text
Co-authored-by: Mikhail Golubev <mikhail.golubev@jetbrains.com>

(cherry picked from commit cab3cb1bbb1316462ee3fd37e735765b31b8d5e8)

GitOrigin-RevId: e3288e503d714a1f76bd1a2e6a0553770e1cbad5
This commit is contained in:
Daniil Kalinin
2024-10-22 11:19:18 +02:00
committed by intellij-monorepo-bot
parent 0f40a088df
commit ea62ea04bc
3 changed files with 31 additions and 68 deletions

View File

@@ -1066,7 +1066,7 @@ INSP.type.checker.expected.types.prefix=Possible type(s):
INSP.type.checker.unexpected.argument.from.paramspec=Unexpected argument (from ParamSpec ''{0}'')
INSP.type.checker.unfilled.parameter.for.paramspec=Parameter ''{0}'' unfilled (from ParamSpec ''{1}'')
INSP.type.checker.unfilled.vararg=Parameter ''{0}'' unfilled, expected ''{1}''
INSP.type.checker.assigned.value.do.not.match.expected.type.from.dunder.set=Assigned type ''{0}'' do not match expected type ''{1}'' of value from '__set__' descriptor of class ''{2}''
INSP.type.checker.expected.type.from.dunder.set.got.type.instead=Expected type ''{0}'' (from ''__set__''), got ''{1}'' instead
# PyTypedDictInspection
INSP.NAME.typed.dict=Invalid TypedDict definition and usages

View File

@@ -5,9 +5,9 @@ import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
@@ -20,7 +20,6 @@ import com.jetbrains.python.documentation.PythonDocumentationProvider;
import com.jetbrains.python.inspections.quickfix.PyMakeFunctionReturnTypeQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.types.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -135,9 +134,19 @@ public class PyTypeCheckerInspection extends PyInspection {
if (owner instanceof PyClass) return;
final PyExpression value = node.findAssignedValue();
if (value == null) return;
final PyType expected = myTypeEvalContext.getType(node);
final PyType actual = tryPromotingType(value, expected);
boolean descriptor = false;
PyType expected = myTypeEvalContext.getType(node);
Ref<PyType> classAttrType = getClassAttributeType(node);
if (classAttrType != null) {
Ref<PyType> dunderSetValueType = PyDescriptorTypeUtil.getExpectedValueTypeForDunderSet(node, classAttrType.get(), myTypeEvalContext);
if (dunderSetValueType != null) {
expected = dunderSetValueType.get();
descriptor = true;
}
}
final PyType actual = tryPromotingType(value, expected);
if (expected != null && actual instanceof PyTypedDictType) {
if (reportTypedDictProblems(expected, (PyTypedDictType)actual, value)) return;
}
@@ -145,62 +154,17 @@ public class PyTypeCheckerInspection extends PyInspection {
if (!PyTypeChecker.match(expected, actual, myTypeEvalContext)) {
String expectedName = PythonDocumentationProvider.getVerboseTypeName(expected, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(actual, myTypeEvalContext);
registerProblem(value, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName));
}
matchAssignedAndExpectedDunderSetDescriptorValue(node, value);
}
private void matchAssignedAndExpectedDunderSetDescriptorValue(@NotNull PyTargetExpression targetExpression,
@NotNull PyExpression assignedValue) {
final ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(targetExpression);
if (scopeOwner == null) return;
Pair<PyType, String> infoFromDunderSet =
getExpectedTypeFromDunderSet(targetExpression, scopeOwner, myTypeEvalContext);
if (infoFromDunderSet == null) return;
final PyType expectedTypeFromDunderSet = infoFromDunderSet.first;
final PyType actual = tryPromotingType(assignedValue, expectedTypeFromDunderSet);
String actualName = PythonDocumentationProvider.getTypeName(actual, myTypeEvalContext);
if (expectedTypeFromDunderSet != null && !PyTypeChecker.match(expectedTypeFromDunderSet, actual, myTypeEvalContext)) {
String expectedName =
PythonDocumentationProvider.getVerboseTypeName(expectedTypeFromDunderSet, myTypeEvalContext);
String className = infoFromDunderSet.second;
if (className != null) {
registerProblem(assignedValue,
PyPsiBundle.message("INSP.type.checker.assigned.value.do.not.match.expected.type.from.dunder.set", actualName,
expectedName, className));
}
registerProblem(value, descriptor ?
PyPsiBundle.message("INSP.type.checker.expected.type.from.dunder.set.got.type.instead", actualName, expectedName) :
PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName));
}
}
private Pair<PyType, String> getExpectedTypeFromDunderSet(@NotNull PyTargetExpression targetExpression,
@NotNull ScopeOwner scopeOwner,
@NotNull TypeEvalContext context) {
PyExpression referenceExpressionFromTarget = PyUtil.createExpressionFromFragment(targetExpression.getText(), scopeOwner);
if (referenceExpressionFromTarget == null) return null;
PyType referenceType = myTypeEvalContext.getType(referenceExpressionFromTarget);
if (referenceType instanceof PyClassType classType) {
Ref<PyType> expectedTypeRefFromSet =
PyDescriptorTypeUtil.getExpectedValueTypeForDunderSet(targetExpression, classType, context);
if (expectedTypeRefFromSet != null) {
final PyResolveContext resolveContext = PyResolveContext.noProperties(context);
final List<? extends RatedResolveResult> members =
classType.resolveMember(PyNames.DUNDER_SET, targetExpression, AccessDirection.READ,
resolveContext);
if (members == null || members.isEmpty() || !(members.get(0).getElement() instanceof PyFunction dunderSetFunc)) return null;
PyClass classContainingDunderSet = dunderSetFunc.getContainingClass();
if (classContainingDunderSet == null || classContainingDunderSet.getName() == null) return null;
return Pair.create(Ref.deref(expectedTypeRefFromSet), classContainingDunderSet.getName());
}
}
return null;
private @Nullable Ref<PyType> getClassAttributeType(@NotNull PyTargetExpression attribute) {
if (!attribute.isQualified()) return null;
PsiElement definition = attribute.getReference(PyResolveContext.defaultContext(myTypeEvalContext)).resolve();
if (!(definition instanceof PyTargetExpression attrDefinition && PyUtil.isAttribute(attrDefinition))) return null;
return Ref.create(myTypeEvalContext.getType(attrDefinition));
}
private boolean reportTypedDictProblems(@NotNull PyType expected, @NotNull PyTypedDictType actual, @NotNull PyExpression value) {

View File

@@ -2183,8 +2183,8 @@ def foo(param: str | int) -> TypeGuard[str]:
t = Test()
t.member = "str"
t.member = <warning descr="Assigned type 'int' do not match expected type 'str' of value from __set__ descriptor of class 'MyDescriptor'">123</warning>
t.member = <warning descr="Assigned type 'Type[list]' do not match expected type 'str' of value from __set__ descriptor of class 'MyDescriptor'">list</warning>
t.member = <warning descr="Expected type 'int' (from '__set__'), got 'str' instead">123</warning>
t.member = <warning descr="Expected type 'Type[list]' (from '__set__'), got 'str' instead">list</warning>
""");
}
@@ -2201,8 +2201,8 @@ def foo(param: str | int) -> TypeGuard[str]:
t = Test()
t.member = "str"
t.member = <warning descr="Assigned type 'int' do not match expected type 'str' of value from __set__ descriptor of class 'MyDescriptor'">123</warning>
t.member = <warning descr="Assigned type 'Type[list]' do not match expected type 'str' of value from __set__ descriptor of class 'MyDescriptor'">list</warning>
t.member = <warning descr="Expected type 'int' (from '__set__'), got 'str' instead">123</warning>
t.member = <warning descr="Expected type 'Type[list]' (from '__set__'), got 'str' instead">list</warning>
""");
}
@@ -2234,11 +2234,10 @@ def foo(param: str | int) -> TypeGuard[str]:
t = Test()
t.member = "abc"
t.member = <warning descr="Assigned type 'int' do not match expected type 'str' of value from __set__ descriptor of class 'MyDescriptor'">42</warning>
t.member = <warning descr="Expected type 'int' (from '__set__'), got 'str' instead">42</warning>
p = Prod()
p.member = <warning descr="Assigned type 'str' do not match expected type 'LocalizedString' of value from __set__ descriptor of class 'MyDescriptor'">"abc"</warning>
p.member = <warning descr="Assigned type 'int' do not match expected type 'LocalizedString' of value from __set__ descriptor of class 'MyDescriptor'">42</warning>
p.member = LocalizedString("abc")
p.member = <warning descr="Expected type 'str' (from '__set__'), got 'LocalizedString' instead">"abc"</warning>
p.member = <warning descr="Expected type 'int' (from '__set__'), got 'LocalizedString' instead">42</warning>
""");
}
@@ -2257,8 +2256,8 @@ def foo(param: str | int) -> TypeGuard[str]:
t = Test()
t.member = 42
t.member = <warning descr="Assigned type 'Literal[43]' do not match expected type 'Literal[42]' of value from __set__ descriptor of class 'MyDescriptor'">43</warning>
t.member = <warning descr="Assigned type 'Literal[\\"42\\"]' do not match expected type 'Literal[42]' of value from __set__ descriptor of class 'MyDescriptor'">"42"</warning>
t.member = <warning descr="Expected type 'Literal[43]' (from '__set__'), got 'Literal[42]' instead">43</warning>
t.member = <warning descr="Expected type 'Literal[\\"42\\"]' (from '__set__'), got 'Literal[42]' instead">"42"</warning>
""");
}