mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
PY-41676 Fix hasattr resolve issue str object is not callable
GitOrigin-RevId: aa9583ab678dd48a9264209b9bb2545b435e9d26
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a034f25ce4
commit
3055c4293a
@@ -1475,18 +1475,4 @@ public abstract class PyCommonResolveTest extends PyCommonResolveTestCase {
|
||||
final TypeEvalContext context = TypeEvalContext.codeInsightFallback(myFixture.getProject());
|
||||
assertEmpty(file.findTopLevelAttribute("t").multiResolveAssignedValue(PyResolveContext.defaultContext().withTypeEvalContext(context)));
|
||||
}
|
||||
|
||||
// PY-10184
|
||||
public void testHasattrResolveTrueIfBranch() {
|
||||
PsiElement targetElement = resolve();
|
||||
assertInstanceOf(targetElement, PyStringLiteralExpression.class);
|
||||
assertEquals("ajjj", ((PyStringLiteralExpression)targetElement).getStringValue());
|
||||
}
|
||||
|
||||
// PY-10184
|
||||
public void testHasattrResolveConditionalExpression() {
|
||||
PsiElement targetElement = resolve();
|
||||
assertInstanceOf(targetElement, PyStringLiteralExpression.class);
|
||||
assertEquals("fld", ((PyStringLiteralExpression)targetElement).getStringValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.impl.references.PyFromImportNameReference;
|
||||
import com.jetbrains.python.psi.impl.references.PyImportReference;
|
||||
import com.jetbrains.python.psi.impl.references.PyOperatorReference;
|
||||
import com.jetbrains.python.psi.impl.references.hasattr.PyHasAttrHelper;
|
||||
import com.jetbrains.python.psi.resolve.ImportedResolveResult;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
|
||||
@@ -185,7 +186,7 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor
|
||||
final PyQualifiedExpression qExpr = (PyQualifiedExpression)node;
|
||||
final PyExpression qualifier = qExpr.getQualifier();
|
||||
final String name = node.getName();
|
||||
if (qualifier != null && name != null && isGuardedByHasattr(qualifier, name)) {
|
||||
if (qualifier != null && name != null && PyHasAttrHelper.INSTANCE.getNamesFromHasAttrs(node, qualifier).contains(name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -686,45 +687,6 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isGuardedByHasattr(@NotNull final PyElement node, @NotNull final String name) {
|
||||
final String nodeName = node.getName();
|
||||
if (nodeName != null) {
|
||||
final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(node, nodeName);
|
||||
PyElement e = PsiTreeUtil.getParentOfType(node, PyConditionalStatementPart.class, PyConditionalExpression.class);
|
||||
while (e != null && PsiTreeUtil.isAncestor(owner, e, true)) {
|
||||
final ArrayList<PyCallExpression> calls = new ArrayList<>();
|
||||
PyExpression cond = null;
|
||||
if (e instanceof PyConditionalStatementPart) {
|
||||
cond = ((PyConditionalStatementPart)e).getCondition();
|
||||
}
|
||||
else if (e instanceof PyConditionalExpression && PsiTreeUtil.isAncestor(((PyConditionalExpression)e).getTruePart(), node, true)) {
|
||||
cond = ((PyConditionalExpression)e).getCondition();
|
||||
}
|
||||
if (cond instanceof PyCallExpression) {
|
||||
calls.add((PyCallExpression)cond);
|
||||
}
|
||||
if (cond != null) {
|
||||
final PyCallExpression[] callExpressions = PsiTreeUtil.getChildrenOfType(cond, PyCallExpression.class);
|
||||
if (callExpressions != null) {
|
||||
calls.addAll(Arrays.asList(callExpressions));
|
||||
}
|
||||
for (PyCallExpression call : calls) {
|
||||
final PyExpression callee = call.getCallee();
|
||||
final PyExpression[] args = call.getArguments();
|
||||
// TODO: Search for `node` aliases using aliases analysis
|
||||
if (callee != null && "hasattr".equals(callee.getName()) && args.length == 2 &&
|
||||
nodeName.equals(args[0].getName()) && args[1] instanceof PyStringLiteralExpression &&
|
||||
((PyStringLiteralExpression)args[1]).getStringValue().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
e = PsiTreeUtil.getParentOfType(e, PyConditionalStatementPart.class);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isContainingFileImportAllowed(PyElement node, PsiFile target) {
|
||||
return PyImportStatementNavigator.getImportStatementByElement(node) == null && target.getName().equals(PyNames.INIT_DOT_PY);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -139,8 +141,6 @@ public class PyQualifiedReference extends PyReferenceImpl {
|
||||
addDocReference(ret, qualifier, qualifierType);
|
||||
}
|
||||
|
||||
PyHasAttrHelper.INSTANCE.addHasAttrResolveResults(myElement, referencedName, qualifier, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -242,14 +242,16 @@ public class PyQualifiedReference extends PyReferenceImpl {
|
||||
else {
|
||||
final PyClassType guessedType = guessClassTypeByName();
|
||||
if (guessedType != null) {
|
||||
Collections.addAll(variants, getTypeCompletionVariants(myElement, guessedType));
|
||||
Collections.addAll(variants, getTypeCompletionVariants(myElement, guessedType, ctx));
|
||||
}
|
||||
if (qualifier instanceof PyReferenceExpression) {
|
||||
Collections.addAll(variants, collectSeenMembers(qualifier.getText()));
|
||||
Collections.addAll(variants, collectSeenMembers(qualifier.getText(), ctx));
|
||||
}
|
||||
}
|
||||
|
||||
PyHasAttrHelper.INSTANCE.addHasAttrCompletionResults(element, qualifier, namesAlready, variants);
|
||||
StreamEx.of(PyHasAttrHelper.INSTANCE.getNamesFromHasAttrs(element, qualifier))
|
||||
.filter(it -> namesAlready.add(it))
|
||||
.into(variants);
|
||||
|
||||
return variants.toArray();
|
||||
}
|
||||
@@ -290,7 +292,7 @@ public class PyQualifiedReference extends PyReferenceImpl {
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object[] collectSeenMembers(final String text) {
|
||||
private Object[] collectSeenMembers(final String text, ProcessingContext context) {
|
||||
final Set<String> members = new HashSet<>();
|
||||
myElement.getContainingFile().accept(new PyRecursiveElementVisitor() {
|
||||
@Override
|
||||
@@ -318,12 +320,23 @@ public class PyQualifiedReference extends PyReferenceImpl {
|
||||
}
|
||||
});
|
||||
List<LookupElement> results = new ArrayList<>(members.size());
|
||||
final Set<String> namesAlready = visitedNames(context);
|
||||
for (String member : members) {
|
||||
results.add(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(LookupElementBuilder.create(member)));
|
||||
namesAlready.add(member);
|
||||
}
|
||||
return ArrayUtil.toObjectArray(results);
|
||||
}
|
||||
|
||||
private static @NotNull Set<String> visitedNames(@NotNull ProcessingContext context) {
|
||||
Set<String> names = context.get(PyType.CTX_NAMES);
|
||||
if (names == null) {
|
||||
names = new HashSet<>();
|
||||
context.put(PyType.CTX_NAMES, names);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expressions accessible from scope of "anchor" with names that start with provided "qualifierQName".
|
||||
* Can be used for completion.
|
||||
|
||||
@@ -801,6 +801,10 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
|
||||
|
||||
protected static Object[] getTypeCompletionVariants(PyExpression pyExpression, PyType type) {
|
||||
ProcessingContext context = new ProcessingContext();
|
||||
return getTypeCompletionVariants(pyExpression, type, context);
|
||||
}
|
||||
|
||||
protected static Object[] getTypeCompletionVariants(PyExpression pyExpression, PyType type, ProcessingContext context) {
|
||||
context.put(PyType.CTX_NAMES, new HashSet<>());
|
||||
return type.getCompletionVariants(pyExpression.getName(), pyExpression, context);
|
||||
}
|
||||
|
||||
@@ -1,65 +1,32 @@
|
||||
package com.jetbrains.python.psi.impl.references.hasattr
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.PlatformIcons
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.ResolveResultList
|
||||
import com.jetbrains.python.psi.resolve.RatedResolveResult
|
||||
|
||||
object PyHasAttrHelper {
|
||||
fun addHasAttrResolveResults(psiElement: PsiElement, referencedName: String, qualifier: PyExpression, ret: ResolveResultList) {
|
||||
val hasAttrVariants = getHasAttrVariantsFromContext(psiElement, qualifier)
|
||||
for (variant in hasAttrVariants.keys) {
|
||||
if (variant == referencedName) {
|
||||
ret.add(RatedResolveResult(RatedResolveResult.RATE_NORMAL, hasAttrVariants[variant]))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
fun getNamesFromHasAttrs(psiElement: PsiElement, qualifier: PyExpression): Set<String> =
|
||||
getHasAttrVariantsFromContext(psiElement, qualifier)
|
||||
|
||||
fun addHasAttrCompletionResults(psiElement: PsiElement, qualifier: PyExpression,
|
||||
namesAlready: MutableSet<String>, variants: MutableCollection<Any>) {
|
||||
for (variant in variants) {
|
||||
if (variant is LookupElement) {
|
||||
namesAlready.add(variant.lookupString)
|
||||
}
|
||||
else {
|
||||
namesAlready.add(variant.toString())
|
||||
}
|
||||
}
|
||||
|
||||
for (variant in getHasAttrVariantsFromContext(psiElement, qualifier).keys) {
|
||||
if (!namesAlready.contains(variant)) {
|
||||
variants.add(LookupElementBuilder.create(variant)
|
||||
.withTypeText(PyNames.HAS_ATTR)
|
||||
.withIcon(PlatformIcons.FIELD_ICON))
|
||||
namesAlready.add(variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHasAttrVariantsFromContext(psiElement: PsiElement, qualifier: PyExpression): Map<String, PsiElement> {
|
||||
val result = hashMapOf<String, PsiElement>()
|
||||
result.putAll(getHasAttrVariantsFromAnd(psiElement, qualifier))
|
||||
result.putAll(getHasAttrVariantsFromConditions(psiElement, qualifier))
|
||||
private fun getHasAttrVariantsFromContext(psiElement: PsiElement, qualifier: PyExpression): Set<String> {
|
||||
val result = hashSetOf<String>()
|
||||
result.addAll(getHasAttrVariantsFromAnd(psiElement, qualifier))
|
||||
result.addAll(getHasAttrVariantsFromConditions(psiElement, qualifier))
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getHasAttrVariantsFromAnd(psiElement: PsiElement, qualifier: PyExpression): Map<String, PsiElement> {
|
||||
val result = hashMapOf<String, PsiElement>()
|
||||
private fun getHasAttrVariantsFromAnd(psiElement: PsiElement, qualifier: PyExpression): Set<String> {
|
||||
val result = hashSetOf<String>()
|
||||
val binaryExpr = PsiTreeUtil.getParentOfType(psiElement, PyBinaryExpression::class.java) ?: return result
|
||||
if (!binaryExpr.isOperator(PyNames.AND)) return result
|
||||
if (!PsiTreeUtil.isAncestor(binaryExpr.rightExpression, psiElement, false)) return result
|
||||
result.putAll(getHasAttrVisitorResultOn(binaryExpr.leftExpression, qualifier))
|
||||
result.addAll(getHasAttrVisitorResultOn(binaryExpr.leftExpression, qualifier))
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getHasAttrVariantsFromConditions(psiElement: PsiElement, qualifier: PyExpression): Map<String, PsiElement> {
|
||||
val result = hashMapOf<String, PsiElement>()
|
||||
private fun getHasAttrVariantsFromConditions(psiElement: PsiElement, qualifier: PyExpression): Set<String> {
|
||||
val result = hashSetOf<String>()
|
||||
|
||||
var curParent = PsiTreeUtil.getParentOfType(psiElement, PyIfPart::class.java, PyConditionalExpression::class.java)
|
||||
while (curParent != null) {
|
||||
@@ -69,7 +36,7 @@ object PyHasAttrHelper {
|
||||
else -> null
|
||||
}
|
||||
if (condition != null) {
|
||||
result.putAll(getHasAttrVisitorResultOn(condition, qualifier))
|
||||
result.addAll(getHasAttrVisitorResultOn(condition, qualifier))
|
||||
}
|
||||
curParent = PsiTreeUtil.getParentOfType(curParent, PyIfPart::class.java, PyConditionalExpression::class.java)
|
||||
}
|
||||
@@ -77,9 +44,9 @@ object PyHasAttrHelper {
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getHasAttrVisitorResultOn(psiElement: PsiElement, qualifier: PyExpression): Map<String, PsiElement> {
|
||||
if (qualifier !is PyReferenceExpression) return hashMapOf()
|
||||
val resolvedQualifier = qualifier.reference.resolve() ?: return hashMapOf()
|
||||
private fun getHasAttrVisitorResultOn(psiElement: PsiElement, qualifier: PyExpression): Set<String> {
|
||||
if (qualifier !is PyReferenceExpression) return emptySet()
|
||||
val resolvedQualifier = qualifier.reference.resolve() ?: return emptySet()
|
||||
val pyHasAttrVisitor = PyHasAttrVisitor(resolvedQualifier)
|
||||
psiElement.accept(pyHasAttrVisitor)
|
||||
return pyHasAttrVisitor.result
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package com.jetbrains.python.psi.impl.references.hasattr
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionUtilCoreImpl
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.PyTokenTypes
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
|
||||
class PyHasAttrVisitor(private val resolvedQualifier: PsiElement) : PyRecursiveElementVisitor() {
|
||||
val result = hashMapOf<String, PsiElement>()
|
||||
val result = hashSetOf<String>()
|
||||
private var myPositive: Boolean = true
|
||||
|
||||
override fun visitPyPrefixExpression(node: PyPrefixExpression) {
|
||||
@@ -39,9 +36,7 @@ class PyHasAttrVisitor(private val resolvedQualifier: PsiElement) : PyRecursiveE
|
||||
if (firstArg.reference.isReferenceTo(resolvedQualifier)) {
|
||||
val variant = attrName.stringValue
|
||||
if (StringUtil.isJavaIdentifier(variant)) {
|
||||
if (!result.containsKey(variant)) {
|
||||
result[variant] = attrName
|
||||
}
|
||||
result.add(variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,16 @@ public class PyCallingNonCallableInspectionTest extends PyInspectionTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
// PY-41676
|
||||
public void testThereIsNoInspectionOnCallProtectedByHasattr() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.getLatest(),
|
||||
() -> doTestByText("def test(obj):\n" +
|
||||
" if hasattr(obj, \"anything\"):\n" +
|
||||
" pkgs = obj.anything()")
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user