mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-83047 False positive "This code is unreachable" after attribute assignment to None under an if
Merge-request: IJ-MR-173356 Merged-by: Aleksandr Govenko <aleksandr.govenko@jetbrains.com> GitOrigin-RevId: 08669d49a831548c354096fbb5cbec1a155da4e2
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7d7c958cde
commit
9bbd8452a0
+10
-6
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
@@ -36,7 +37,10 @@ import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* User: ktisha
|
||||
@@ -73,12 +77,12 @@ public final class PyAttributeOutsideInitInspection extends PyInspection {
|
||||
}
|
||||
|
||||
final Map<String, Property> localProperties = containingClass.getProperties();
|
||||
final Map<String, PyTargetExpression> declaredAttributes = new HashMap<>();
|
||||
final MultiMap<String, PyTargetExpression > declaredAttributes = new MultiMap<>();
|
||||
final Set<String> inheritedProperties = new HashSet<>();
|
||||
|
||||
StreamEx.of(containingClass.getClassAttributes())
|
||||
.filter(attribute -> !localProperties.containsKey(attribute.getName()))
|
||||
.forEach(attribute -> declaredAttributes.put(attribute.getName(), attribute));
|
||||
.forEach(attribute -> declaredAttributes.putValue(attribute.getName(), attribute));
|
||||
|
||||
final PyFunction initMethod = containingClass.findMethodByName(PyNames.INIT, false, myTypeEvalContext);
|
||||
if (initMethod != null) {
|
||||
@@ -91,13 +95,13 @@ public final class PyAttributeOutsideInitInspection extends PyInspection {
|
||||
}
|
||||
|
||||
for (PyTargetExpression classAttr : superClass.getClassAttributes()) {
|
||||
declaredAttributes.put(classAttr.getName(), classAttr);
|
||||
declaredAttributes.putValue(classAttr.getName(), classAttr);
|
||||
}
|
||||
|
||||
inheritedProperties.addAll(superClass.getProperties().keySet());
|
||||
}
|
||||
|
||||
final Map<String, PyTargetExpression> attributes = new HashMap<>();
|
||||
final MultiMap<String, PyTargetExpression> attributes = new MultiMap<>();
|
||||
PyClassImpl.collectInstanceAttributes(node, attributes);
|
||||
|
||||
for (PyTargetExpression attribute : attributes.values()) {
|
||||
@@ -138,7 +142,7 @@ public final class PyAttributeOutsideInitInspection extends PyInspection {
|
||||
|
||||
private static boolean isDefinedByProperty(@NotNull PyTargetExpression attribute,
|
||||
@NotNull Collection<Property> properties,
|
||||
@NotNull Map<String, PyTargetExpression> attributesInInit) {
|
||||
@NotNull MultiMap<String, PyTargetExpression> attributesInInit) {
|
||||
return StreamEx.of(properties)
|
||||
.filter(it -> isSetBy(attribute, it))
|
||||
.anyMatch(it -> attributesInInit.containsKey(it.getName()));
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache
|
||||
@@ -192,10 +193,10 @@ class PyFinalInspection : PyInspection() {
|
||||
val classLevelFinals = mutableMapOf<String?, PyTargetExpression>()
|
||||
cls.classAttributes.forEach { if (isFinal(it)) classLevelFinals[it.name] = it }
|
||||
|
||||
val initAttributes = mutableMapOf<String, PyTargetExpression>()
|
||||
val initAttributes = MultiMap<String, PyTargetExpression>()
|
||||
cls.findMethodByName(PyNames.INIT, false, myTypeEvalContext)?.let { PyClassImpl.collectInstanceAttributes(it, initAttributes) }
|
||||
|
||||
return Pair(classLevelFinals, initAttributes)
|
||||
return Pair(classLevelFinals, initAttributes.toHashMap().mapValues { it.value.first() })
|
||||
}
|
||||
|
||||
private fun getDeclaredClassAndInstanceFinals(cls: PyClass): Pair<Map<String, PyTargetExpression>, Map<String, PyTargetExpression>> {
|
||||
@@ -212,10 +213,10 @@ class PyFinalInspection : PyInspection() {
|
||||
}
|
||||
|
||||
cls.findMethodByName(PyNames.INIT, false, myTypeEvalContext)?.let { init ->
|
||||
val attributesInInit = mutableMapOf<String, PyTargetExpression>()
|
||||
val attributesInInit = MultiMap<String, PyTargetExpression>()
|
||||
PyClassImpl.collectInstanceAttributes(init, attributesInInit)
|
||||
attributesInInit.keys.removeAll(instanceFinals.keys)
|
||||
instanceFinals += attributesInInit.filterValues { isFinal(it) }
|
||||
attributesInInit.keySet().removeAll(instanceFinals.keys)
|
||||
instanceFinals += attributesInInit.toHashMap().mapValues { it.value.first() }.filterValues { isFinal(it) }
|
||||
}
|
||||
|
||||
return Pair(classFinals, instanceFinals)
|
||||
@@ -281,9 +282,9 @@ class PyFinalInspection : PyInspection() {
|
||||
private fun checkInstanceFinalsOutsideInit(method: PyFunction) {
|
||||
if (PyUtil.isInitMethod(method)) return
|
||||
|
||||
val instanceAttributes = mutableMapOf<String, PyTargetExpression>()
|
||||
val instanceAttributes = MultiMap<String, PyTargetExpression>()
|
||||
PyClassImpl.collectInstanceAttributes(method, instanceAttributes)
|
||||
instanceAttributes.values.forEach {
|
||||
instanceAttributes.values().forEach {
|
||||
if (isFinal(it)) registerProblem(it, PyPsiBundle.message("INSP.final.final.attribute.should.be.declared.in.class.body.or.init"))
|
||||
}
|
||||
}
|
||||
@@ -373,9 +374,9 @@ class PyFinalInspection : PyInspection() {
|
||||
for (current in (sequenceOf(cls) + cls.getAncestorClasses(myTypeEvalContext).asSequence())) {
|
||||
val init = current.findMethodByName(PyNames.INIT, false, myTypeEvalContext)
|
||||
if (init != null) {
|
||||
val attributesInInit = mutableMapOf<String, PyTargetExpression>()
|
||||
val attributesInInit = MultiMap<String, PyTargetExpression>()
|
||||
PyClassImpl.collectInstanceAttributes(init, attributesInInit)
|
||||
if (attributesInInit[name]?.let { it != target && isFinal(it) } == true) {
|
||||
if (attributesInInit[name].any { it != target && isFinal(it) }) {
|
||||
@NlsSafe val qualifiedName = (if (cls == current) "" else "${current.name}.") + name
|
||||
registerProblem(target, PyPsiBundle.message("INSP.final.final.target.could.not.be.reassigned", qualifiedName))
|
||||
break
|
||||
|
||||
@@ -182,34 +182,6 @@ public final class PyUtil {
|
||||
|
||||
// TODO: move to a more proper place?
|
||||
|
||||
/**
|
||||
* Determine the type of a special attribute. Currently supported: {@code __class__} and {@code __dict__}.
|
||||
*
|
||||
* @param ref reference to a possible attribute; only qualified references make sense.
|
||||
* @return type, or null (if type cannot be determined, reference is not to a known attribute, etc.)
|
||||
*/
|
||||
public static @Nullable PyType getSpecialAttributeType(@Nullable PyReferenceExpression ref, TypeEvalContext context) {
|
||||
if (ref != null) {
|
||||
PyExpression qualifier = ref.getQualifier();
|
||||
if (qualifier != null) {
|
||||
String attr_name = ref.getReferencedName();
|
||||
if (PyNames.__CLASS__.equals(attr_name)) {
|
||||
PyType qualifierType = context.getType(qualifier);
|
||||
if (qualifierType instanceof PyClassType) {
|
||||
return new PyClassTypeImpl(((PyClassType)qualifierType).getPyClass(), true); // always as class, never instance
|
||||
}
|
||||
}
|
||||
else if (PyNames.DUNDER_DICT.equals(attr_name)) {
|
||||
PyType qualifierType = context.getType(qualifier);
|
||||
if (qualifierType instanceof PyClassType && ((PyClassType)qualifierType).isDefinition()) {
|
||||
return PyBuiltinCache.getInstance(ref).getDictType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that 'thing' is not null; else throws an {@link IncorrectOperationException}.
|
||||
*
|
||||
|
||||
@@ -1148,6 +1148,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
Map<String, ScopeOwner> scopesToSkip = StreamEx.of(getInstanceAttributes())
|
||||
.filter(e -> e.getName() != null)
|
||||
.mapToEntry(e -> e.getName(), e -> ScopeUtil.getScopeOwner(e))
|
||||
.distinct()
|
||||
.toMap();
|
||||
attributes = collectInstanceAttributes(scopesToSkip);
|
||||
myFallbackInstanceAttributes = attributes;
|
||||
@@ -1155,7 +1156,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
}
|
||||
|
||||
private @NotNull List<PyTargetExpression> collectInstanceAttributes(@NotNull Map<String, ScopeOwner> scopesToSkip) {
|
||||
Map<String, PyTargetExpression> result = new HashMap<>();
|
||||
MultiMap<String, PyTargetExpression> result = new MultiMap<>();
|
||||
collectAttributesInConstructors(result, scopesToSkip);
|
||||
final PyFunction[] methods = getMethods();
|
||||
for (PyFunction method : methods) {
|
||||
@@ -1164,14 +1165,14 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
return new ArrayList<>(result.values());
|
||||
}
|
||||
|
||||
private void collectAttributesInConstructors(@NotNull Map<String, PyTargetExpression> result,
|
||||
private void collectAttributesInConstructors(@NotNull MultiMap<String, PyTargetExpression> result,
|
||||
@NotNull Map<String, ScopeOwner> scopesToSkip) {
|
||||
PyFunction newMethod = findMethodByName(PyNames.NEW, false, null);
|
||||
if (newMethod != null) {
|
||||
for (PyTargetExpression target : getTargetExpressions(newMethod)) {
|
||||
String name = target.getName();
|
||||
if (scopesToSkip.get(name) != newMethod) {
|
||||
result.put(name, target);
|
||||
result.putValue(name, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1181,12 +1182,24 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
}
|
||||
}
|
||||
|
||||
public static void collectInstanceAttributes(@NotNull PyFunction method, final @NotNull Map<String, PyTargetExpression> result) {
|
||||
public static void collectInstanceAttributes(@NotNull PyFunction method, final @NotNull MultiMap<String, PyTargetExpression> result) {
|
||||
collectInstanceAttributes(method, result, emptySet(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use collectInstanceAttributes(com.jetbrains.python.psi.PyFunction, com.intellij.util.containers.MultiMap)
|
||||
*/
|
||||
@Deprecated
|
||||
public static void collectInstanceAttributes(@NotNull PyFunction method, final @NotNull Map<String, PyTargetExpression> result) {
|
||||
MultiMap<String, PyTargetExpression> localResult = new MultiMap<>();
|
||||
collectInstanceAttributes(method, localResult, emptySet(), Collections.emptyMap());
|
||||
for (Map.Entry<String, Collection<PyTargetExpression>> entry : localResult.entrySet()) {
|
||||
result.put(entry.getKey(), ContainerUtil.getFirstItem(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectInstanceAttributes(@NotNull PyFunction method,
|
||||
final @NotNull Map<String, PyTargetExpression> result,
|
||||
final @NotNull MultiMap<String, PyTargetExpression> result,
|
||||
@NotNull Set<String> namesToSkip,
|
||||
@NotNull Map<String, ScopeOwner> scopesToSkip) {
|
||||
final PyParameter[] params = method.getParameterList().getParameters();
|
||||
@@ -1198,7 +1211,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
if (!namesToSkip.contains(name) &&
|
||||
scopesToSkip.get(name) != method &&
|
||||
PyUtil.isInstanceAttribute(target)) {
|
||||
result.put(name, target);
|
||||
result.putValue(name, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1354,27 +1367,10 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
|
||||
@Override
|
||||
public boolean processInstanceLevelDeclarations(@NotNull PsiScopeProcessor processor, @Nullable PsiElement location) {
|
||||
final PyFunction instanceMethod = PsiTreeUtil.getStubOrPsiParentOfType(location, PyFunction.class);
|
||||
final PyClass containingClass = instanceMethod != null ? instanceMethod.getContainingClass() : null;
|
||||
if (instanceMethod != null && containingClass != null && CompletionUtilCoreImpl.getOriginalElement(containingClass) == this) {
|
||||
for (PyTargetExpression target : getTargetExpressions(instanceMethod)) {
|
||||
if (PyUtil.isInstanceAttribute(target) && !processor.execute(target, ResolveState.initial())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (PyTargetExpression expr : getInstanceAttributes()) {
|
||||
if (!processor.execute(expr, ResolveState.initial())) return false;
|
||||
}
|
||||
if (!processInstanceAttributesNotInMethod(processor, instanceMethod, getInstanceAttributes())) return false;
|
||||
if (!processInstanceAttributesNotInMethod(processor, instanceMethod, getFallbackInstanceAttributes())) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean processInstanceAttributesNotInMethod(@NotNull PsiScopeProcessor processor,
|
||||
@Nullable PyFunction instanceMethod,
|
||||
@NotNull List<PyTargetExpression> instanceAttributes) {
|
||||
for (PyTargetExpression expr : instanceAttributes) {
|
||||
if (instanceMethod != null && ScopeUtil.getScopeOwner(expr) == instanceMethod) {
|
||||
continue;
|
||||
}
|
||||
for (PyTargetExpression expr : getFallbackInstanceAttributes()) {
|
||||
if (!processor.execute(expr, ResolveState.initial())) return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
+70
-53
@@ -43,8 +43,6 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(PyReferenceExpressionImpl.class);
|
||||
|
||||
private static final int MAX_CFG_ITERATIONS = 30;
|
||||
|
||||
private volatile @Nullable QualifiedName myQualifiedName = null;
|
||||
|
||||
public PyReferenceExpressionImpl(@NotNull ASTNode astNode) {
|
||||
@@ -203,7 +201,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
return descriptorType.get();
|
||||
}
|
||||
|
||||
final PyType callableType = getCallableType(context, key);
|
||||
final PyType callableType = getCallableType(context);
|
||||
if (callableType != null) {
|
||||
return callableType;
|
||||
}
|
||||
@@ -215,7 +213,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
return typeFromTargets;
|
||||
}
|
||||
|
||||
private @Nullable PyType getCallableType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
|
||||
private @Nullable PyType getCallableType(@NotNull TypeEvalContext context) {
|
||||
PyCallExpression callExpression = PyCallExpressionNavigator.getPyCallExpressionByCallee(this);
|
||||
if (callExpression != null) {
|
||||
return getCalleeType(callExpression, PyResolveContext.defaultContext(context));
|
||||
@@ -228,19 +226,45 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
return null;
|
||||
}
|
||||
|
||||
final PyType maybe_type = PyUtil.getSpecialAttributeType(this, context);
|
||||
if (maybe_type != null) return Ref.create(maybe_type);
|
||||
final PyExpression qualifier = getQualifier();
|
||||
if (qualifier == null) return null;
|
||||
|
||||
final String attrName = getName();
|
||||
if (attrName == null) return null;
|
||||
|
||||
final Ref<PyType> typeOfProperty = getTypeOfProperty(context);
|
||||
final PyType qualifierType = context.getType(qualifier);
|
||||
|
||||
final PyType dunderClassType = getDunderClassType(qualifierType, attrName);
|
||||
if (dunderClassType != null) return Ref.create(dunderClassType);
|
||||
|
||||
final Ref<PyType> typeOfProperty = getTypeOfProperty(qualifierType, attrName, context);
|
||||
if (typeOfProperty != null) {
|
||||
return typeOfProperty;
|
||||
}
|
||||
|
||||
// This code performs a backwards traversal through the Control Flow Graph to analyze assignments.
|
||||
// It searches for WRITE instructions involving `qualifier.this_name` with the following behavior:
|
||||
//
|
||||
// 1. If WRITE instructions are found on all possible execution paths:
|
||||
// - Returns a union type combining the types from all getType() calls on those instructions
|
||||
//
|
||||
// 2. If a WRITE instruction involving just the `qualifier` is found on any path
|
||||
// (via PyTargetExpression or PyNamedParameter):
|
||||
// - The analysis stops and returns null, ignoring any other paths
|
||||
//
|
||||
// (see PyDefUseUtil.getLatestDefs)
|
||||
//
|
||||
// Note on getType() behavior for PyTargetExpression:
|
||||
// - First queries PyTypeProviders (including PyTypingTypeProvider)
|
||||
// - PyTypingTypeProvider checks if qualifier's class has a type annotation for 'this_name'
|
||||
// and returns that annotated type if found
|
||||
// - If no providers return a type, falls back to returning the type of the assigned value
|
||||
|
||||
final PyType typeByControlFlow = getQualifiedReferenceTypeByControlFlow(context);
|
||||
if (typeByControlFlow != null) {
|
||||
return Ref.create(typeByControlFlow);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -298,16 +322,6 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Ref<PyType> getTypeOfProperty(@NotNull TypeEvalContext context) {
|
||||
final PyExpression qualifier = getQualifier();
|
||||
final String name = getName();
|
||||
if (name != null && qualifier != null) {
|
||||
final PyType qualifierType = context.getType(qualifier);
|
||||
return getTypeOfProperty(qualifierType, name, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Ref<PyType> getTypeOfProperty(@Nullable PyType qualifierType, @NotNull String name, @NotNull TypeEvalContext context) {
|
||||
if (qualifierType instanceof PyClassType classType) {
|
||||
final PyClass pyClass = classType.getPyClass();
|
||||
@@ -346,6 +360,14 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable PyType getDunderClassType(@Nullable PyType qualifierType, @NotNull String attrName) {
|
||||
if (qualifierType instanceof PyClassType classType && PyNames.__CLASS__.equals(attrName)) {
|
||||
// PyInstantiableType#toClass() does not work here, as we also need to remove generic parameters
|
||||
return new PyClassTypeImpl(classType.getPyClass(), true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable PyType getTypeFromProviders(@NotNull TypeEvalContext context) {
|
||||
for (PyTypeProvider provider : PyTypeProvider.EP_NAME.getExtensionList()) {
|
||||
try {
|
||||
@@ -484,45 +506,40 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
@NotNull ScopeOwner scopeOwner) {
|
||||
final PyAugAssignmentStatement augAssignment = PsiTreeUtil.getParentOfType(anchor, PyAugAssignmentStatement.class);
|
||||
final PyElement element = augAssignment != null ? augAssignment : anchor;
|
||||
try {
|
||||
final List<Instruction> defs = PyDefUseUtil.getLatestDefs(scopeOwner, name, element, true, false, context);
|
||||
// null means empty set of possible types, Ref(null) means Any
|
||||
final @Nullable Ref<PyType> combinedType = StreamEx.of(defs)
|
||||
.map(instr -> {
|
||||
if (instr.getElement() == anchor) {
|
||||
// exclude recursive definition (example: type of 'i++' inside a loop)
|
||||
return null;
|
||||
}
|
||||
if (instr instanceof ReadWriteInstruction readWriteInstruction) {
|
||||
return readWriteInstruction.getType(context, anchor);
|
||||
}
|
||||
if (instr instanceof ConditionalInstruction conditionalInstruction) {
|
||||
if (context.getType((PyTypedElement)conditionalInstruction.getCondition()) instanceof PyNarrowedType narrowedType
|
||||
&& narrowedType.isBound()) {
|
||||
var arguments = narrowedType.getOriginal().getArguments(null);
|
||||
if (!arguments.isEmpty()) {
|
||||
var firstArgument = arguments.get(0);
|
||||
PyType type = narrowedType.getNarrowedType();
|
||||
if (firstArgument instanceof PyReferenceExpression && type != null) {
|
||||
@Nullable PyType initial = context.getType(firstArgument);
|
||||
boolean positive = conditionalInstruction.getResult() ^ narrowedType.getNegated();
|
||||
if (narrowedType.getTypeIs()) {
|
||||
return PyTypeAssertionEvaluator.createAssertionType(initial, type, positive, false, context);
|
||||
}
|
||||
return Ref.create((positive) ? type : initial);
|
||||
final List<Instruction> defs = PyDefUseUtil.getLatestDefs(scopeOwner, name, element, true, false, context);
|
||||
// null means empty set of possible types, Ref(null) means Any
|
||||
final @Nullable Ref<PyType> combinedType = StreamEx.of(defs)
|
||||
.map(instr -> {
|
||||
if (instr.getElement() == anchor) {
|
||||
// exclude recursive definition (example: type of 'i++' inside a loop)
|
||||
return null;
|
||||
}
|
||||
if (instr instanceof ReadWriteInstruction readWriteInstruction) {
|
||||
return readWriteInstruction.getType(context, anchor);
|
||||
}
|
||||
if (instr instanceof ConditionalInstruction conditionalInstruction) {
|
||||
if (context.getType((PyTypedElement)conditionalInstruction.getCondition()) instanceof PyNarrowedType narrowedType
|
||||
&& narrowedType.isBound()) {
|
||||
var arguments = narrowedType.getOriginal().getArguments(null);
|
||||
if (!arguments.isEmpty()) {
|
||||
var firstArgument = arguments.get(0);
|
||||
PyType type = narrowedType.getNarrowedType();
|
||||
if (firstArgument instanceof PyReferenceExpression && type != null) {
|
||||
@Nullable PyType initial = context.getType(firstArgument);
|
||||
boolean positive = conditionalInstruction.getResult() ^ narrowedType.getNegated();
|
||||
if (narrowedType.getTypeIs()) {
|
||||
return PyTypeAssertionEvaluator.createAssertionType(initial, type, positive, false, context);
|
||||
}
|
||||
return Ref.create((positive) ? type : initial);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.nonNull()
|
||||
.collect(PyTypeUtil.toUnionFromRef());
|
||||
return Ref.deref(combinedType);
|
||||
}
|
||||
catch (PyDefUseUtil.InstructionNotFoundException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.nonNull()
|
||||
.collect(PyTypeUtil.toUnionFromRef());
|
||||
return Ref.deref(combinedType);
|
||||
}
|
||||
|
||||
public static @Nullable Ref<PyType> getReferenceTypeFromProviders(@NotNull PsiElement target,
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.PyLanguageFacadeKt;
|
||||
import com.jetbrains.python.codeInsight.controlflow.*;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
@@ -69,10 +70,13 @@ public final class PyDefUseUtil {
|
||||
if (startNum < 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
QualifiedName varQname = QualifiedName.fromDottedString(varName);
|
||||
|
||||
LanguageLevel languageLevel = PyLanguageFacadeKt.getEffectiveLanguageLevel(anchor.getContainingFile());
|
||||
final Collection<Instruction> result = new LinkedHashSet<>();
|
||||
final HashMap<PyCallSiteExpression, ConditionalInstruction> pendingTypeGuard = new HashMap<>();
|
||||
final Ref<@NotNull Boolean> foundPrefixWrite = Ref.create(false);
|
||||
iteratePrev(startNum, controlFlow,
|
||||
instruction -> {
|
||||
if (instruction instanceof PyWithContextExitInstruction withExit) {
|
||||
@@ -101,8 +105,16 @@ public final class PyDefUseUtil {
|
||||
? TypeEvalContext.codeAnalysis(context.getOrigin().getProject(), context.getOrigin())
|
||||
: TypeEvalContext.codeInsightFallback(context.getOrigin().getProject());
|
||||
if (newContext.getType(typedElement) instanceof PyNarrowedType narrowedType && narrowedType.isBound()) {
|
||||
if (varName.equals(narrowedType.getQname())) {
|
||||
pendingTypeGuard.put(narrowedType.getOriginal(), conditionalInstruction);
|
||||
String narrowedQname = narrowedType.getQname();
|
||||
if (narrowedQname != null) {
|
||||
if (isQualifiedBy(varQname, narrowedQname)) {
|
||||
foundPrefixWrite.set(true);
|
||||
return ControlFlowUtil.Operation.BREAK;
|
||||
}
|
||||
|
||||
if (narrowedQname.equals(varName)) {
|
||||
pendingTypeGuard.put(narrowedType.getOriginal(), conditionalInstruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +123,17 @@ public final class PyDefUseUtil {
|
||||
final ReadWriteInstruction.ACCESS access = rwInstruction.getAccess();
|
||||
if (access.isWriteAccess() ||
|
||||
acceptTypeAssertions && access.isAssertTypeAccess() && instruction.num() < startNum) {
|
||||
if (Comparing.strEqual(rwInstruction.getName(), varName)) {
|
||||
|
||||
final String name = rwInstruction.getName();
|
||||
|
||||
if (name != null && isQualifiedBy(varQname, name)) {
|
||||
if (isReachableWithVersionChecks(rwInstruction, languageLevel)){
|
||||
foundPrefixWrite.set(true);
|
||||
return ControlFlowUtil.Operation.BREAK;
|
||||
}
|
||||
}
|
||||
|
||||
if (Comparing.strEqual(name, varName)) {
|
||||
if (isReachableWithVersionChecks(rwInstruction, languageLevel)) {
|
||||
result.add(rwInstruction);
|
||||
}
|
||||
@@ -129,9 +151,17 @@ public final class PyDefUseUtil {
|
||||
}
|
||||
return ControlFlowUtil.Operation.NEXT;
|
||||
});
|
||||
if (foundPrefixWrite.get()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
|
||||
private static boolean isQualifiedBy(QualifiedName varQname, @NotNull String qualifier) {
|
||||
QualifiedName elementQname = QualifiedName.fromDottedString(qualifier);
|
||||
return varQname.getComponentCount() > elementQname.getComponentCount() && varQname.matchesPrefix(elementQname);
|
||||
}
|
||||
|
||||
private static int findStartInstructionId(@NotNull PsiElement startAnchor, @NotNull PyControlFlow flow) {
|
||||
PsiElement realCfgAnchor = startAnchor;
|
||||
final PyAugAssignmentStatement augAssignment = PyAugAssignmentStatementNavigator.getStatementByTarget(startAnchor);
|
||||
|
||||
@@ -805,7 +805,7 @@ public class Py3ResolveTest extends PyResolveTestCase {
|
||||
public void testInstanceAttrOtherMethodAndAbove() {
|
||||
final PyTargetExpression target = assertResolvesTo(PyTargetExpression.class, "foo");
|
||||
final PyFunction function = assertInstanceOf(ScopeUtil.getScopeOwner(target), PyFunction.class);
|
||||
assertEquals("f", function.getName());
|
||||
assertEquals("g", function.getName());
|
||||
}
|
||||
|
||||
public void testInstanceAttrBelowAndOtherMethodAbove() {
|
||||
|
||||
@@ -17,6 +17,108 @@ import java.util.Map;
|
||||
|
||||
public class Py3TypeTest extends PyTestCase {
|
||||
public static final String TEST_DIRECTORY = "/types/";
|
||||
|
||||
// See PyReferenceExpressionImpl.getQualifiedReferenceType for explanations.
|
||||
public void testQualifiedNameResolution() {
|
||||
doTest("str", """
|
||||
class C:
|
||||
def m(self):
|
||||
self.t = 5
|
||||
|
||||
def f(self: C, x: float):
|
||||
self.t = "foo"
|
||||
expr = self.t
|
||||
""");
|
||||
|
||||
doTest("int", """
|
||||
class C:
|
||||
def m(self):
|
||||
self.t: int = 5
|
||||
|
||||
def f(self: C, x: float):
|
||||
self.t = "foo"
|
||||
expr = self.t
|
||||
""");
|
||||
|
||||
doTest("int", """
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.t: int = 5
|
||||
|
||||
def f(self: C, x: float):
|
||||
self.t = "foo"
|
||||
expr = self.t
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-83047
|
||||
public void testQualifiedReferenceTypeNarrowing() {
|
||||
doTest("int | None", """
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.t: int | None = 5
|
||||
|
||||
def f(self, x: float):
|
||||
if x < 0:
|
||||
self.t = None
|
||||
|
||||
expr = self.t
|
||||
""");
|
||||
|
||||
doTest("int", """
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.t: int | None = 5
|
||||
|
||||
def f(self, x: float):
|
||||
if self.t is not None:
|
||||
expr = self.t
|
||||
""");
|
||||
|
||||
doTest("None", """
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.t: int | None = 5
|
||||
|
||||
def f(self, x: float):
|
||||
if self.t is None:
|
||||
expr = self.t
|
||||
""");
|
||||
|
||||
// Same, but as a separate function
|
||||
|
||||
doTest("int | None", """
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.t: int | None = 5
|
||||
|
||||
def f(self: C, x: float):
|
||||
if x < 0:
|
||||
self.t = None
|
||||
|
||||
expr = self.t
|
||||
""");
|
||||
|
||||
doTest("int", """
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.t: int | None = 5
|
||||
|
||||
def f(self: C, x: float):
|
||||
if self.t is not None:
|
||||
expr = self.t
|
||||
""");
|
||||
|
||||
doTest("None", """
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.t: int | None = 5
|
||||
|
||||
def f(self: C, x: float):
|
||||
if self.t is None:
|
||||
expr = self.t
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
Overload signatures for dict.get and dict.pop in builtins.pyi differ slightly,
|
||||
|
||||
@@ -353,7 +353,7 @@ public class PyFinalInspectionTest extends PyInspectionTestCase {
|
||||
self.a: Final[str] = "str"
|
||||
|
||||
def method(self):
|
||||
<warning descr="'Final' attribute should be declared in class body or '__init__'">self.a</warning>: Final[int] = 10
|
||||
<warning descr="'Final' attribute should be declared in class body or '__init__'"><warning descr="Already declared name could not be redefined as 'Final'">self.a</warning></warning>: Final[int] = 10
|
||||
<warning descr="'Final' attribute should be declared in class body or '__init__'">self.b</warning>: Final[int] = 10""")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user