PY-41676 Fix hasattr resolve issue str object is not callable

GitOrigin-RevId: aa9583ab678dd48a9264209b9bb2545b435e9d26
This commit is contained in:
andrey.matveev
2020-04-16 13:44:07 +07:00
committed by intellij-monorepo-bot
parent a034f25ce4
commit 3055c4293a
7 changed files with 52 additions and 115 deletions

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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() {