Fixed unresolved reference for parameters which methods are invoked inside the function body (PY-11541)

We may get stuck in a recursive resolve loop (that results in unresolved
references) if we match arguments to parameters for a call where the
callee is a reference to the named parameter itself.

The loop used to go like this:

1. Get the type of 'foo.bar' in 'foo.bar(foo)'
2. Get the type of 'foo' in 'def f(foo)'
3. Find the structural type for 'foo'
4. Ask nested calls where 'foo' is a parameter
5. Try to match args to params in 'foo.bar(foo)'
6. Resolve 'foo.bar'
7. Get the type of 'foo.bar' in 'foo.bar(foo')
8. Since the 'foo.bar' is evaluating, it's 'null' for preventing
   recursion
9. So 'foo.bar' resolves to 'null' which is false
This commit is contained in:
Andrey Vlasovskikh
2015-05-22 13:07:48 +02:00
parent 6aabd19818
commit f73ae3f9bf
3 changed files with 32 additions and 5 deletions
@@ -349,11 +349,29 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
}
@Nullable
private static PyNamedParameter getParameterByCallArgument(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
final PyCallExpression call = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
if (call != null) {
final PyArgumentList argumentList = call.getArgumentList();
if (argumentList != null) {
private PyNamedParameter getParameterByCallArgument(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
final PyArgumentList argumentList = PsiTreeUtil.getParentOfType(element, PyArgumentList.class);
if (argumentList != null) {
boolean elementIsArgument = false;
for (PyExpression argument : argumentList.getArgumentExpressions()) {
if (PyPsiUtils.flattenParens(argument) == element) {
elementIsArgument = true;
break;
}
}
final PyCallExpression callExpression = argumentList.getCallExpression();
if (elementIsArgument && callExpression != null) {
final PyExpression callee = callExpression.getCallee();
if (callee instanceof PyReferenceExpression) {
final PyReferenceExpression calleeReferenceExpr = (PyReferenceExpression)callee;
final PyExpression firstQualifier = PyPsiUtils.getFirstQualifier(calleeReferenceExpr);
if (firstQualifier != null) {
final PsiReference ref = firstQualifier.getReference();
if (ref != null && ref.isReferenceTo(this)) {
return null;
}
}
}
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final CallArgumentsMapping mapping = argumentList.analyzeCall(resolveContext);
for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getPlainMappedParams().entrySet()) {
@@ -0,0 +1,4 @@
def repl(s):
if not isinstance(s, basestring):
return s
return s.replace(s.replace('a', 'b'), s)
@@ -505,6 +505,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-11541
public void testBaseStringCheck() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {