mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
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:
committed by
intellij-monorepo-bot
parent
0f40a088df
commit
ea62ea04bc
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user