PY-73886 PyArgumentListInspection causes file parsing

GitOrigin-RevId: 8fa6b52a2ac5d28e7148c3e10445ee61fef6c89b
This commit is contained in:
Petr
2024-07-17 18:20:07 +02:00
committed by intellij-monorepo-bot
parent d1dd4331f4
commit 5470f70382
5 changed files with 128 additions and 50 deletions

View File

@@ -35,6 +35,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Supplier;
import static com.intellij.refactoring.changeSignature.ParameterInfo.NEW_PARAMETER;
import static com.jetbrains.python.psi.PyUtil.as;
@@ -47,62 +48,68 @@ public final class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement {
assert mapping.getCallableType() != null;
final PyFunction function = as(mapping.getCallableType().getCallable(), PyFunction.class);
assert function != null;
final PyCallSiteExpression callSiteExpression = mapping.getCallSiteExpression();
int positionalParamAnchor = -1;
final PyParameter[] parameters = function.getParameterList().getParameters();
for (PyParameter parameter : parameters) {
final PyNamedParameter namedParam = parameter.getAsNamed();
final boolean isVararg = namedParam != null && (namedParam.isPositionalContainer() || namedParam.isKeywordContainer());
if (parameter instanceof PySingleStarParameter || parameter.hasDefaultValue() || isVararg) {
break;
Supplier<List<Pair<Integer, PyParameterInfo>>> extraParamsSupplier = () -> {
final PyCallSiteExpression callSiteExpression = mapping.getCallSiteExpression();
int positionalParamAnchor = -1;
final PyParameter[] parameters = function.getParameterList().getParameters();
for (PyParameter parameter : parameters) {
final PyNamedParameter namedParam = parameter.getAsNamed();
final boolean isVararg = namedParam != null && (namedParam.isPositionalContainer() || namedParam.isKeywordContainer());
if (parameter instanceof PySingleStarParameter || parameter.hasDefaultValue() || isVararg) {
break;
}
positionalParamAnchor++;
}
positionalParamAnchor++;
}
final List<Pair<Integer, PyParameterInfo>> newParameters = new ArrayList<>();
final TypeEvalContext context = TypeEvalContext.userInitiated(function.getProject(), callSiteExpression.getContainingFile());
final Set<String> usedParamNames = new HashSet<>();
for (PyExpression arg : mapping.getUnmappedArguments()) {
if (arg instanceof PyKeywordArgument) {
final PyExpression value = ((PyKeywordArgument)arg).getValueExpression();
final String valueText = value != null ? value.getText() : "";
newParameters.add(Pair.create(parameters.length - 1,
new PyParameterInfo(NEW_PARAMETER, ((PyKeywordArgument)arg).getKeyword(), valueText, true)));
final List<Pair<Integer, PyParameterInfo>> newParameters = new ArrayList<>();
final TypeEvalContext context = TypeEvalContext.userInitiated(function.getProject(), callSiteExpression.getContainingFile());
final Set<String> usedParamNames = new HashSet<>();
for (PyExpression arg : mapping.getUnmappedArguments()) {
if (arg instanceof PyKeywordArgument) {
final PyExpression value = ((PyKeywordArgument)arg).getValueExpression();
final String valueText = value != null ? value.getText() : "";
newParameters.add(Pair.create(parameters.length - 1,
new PyParameterInfo(NEW_PARAMETER, ((PyKeywordArgument)arg).getKeyword(), valueText, true)));
}
else {
final String paramName = generateParameterName(arg, function, usedParamNames, context);
newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(NEW_PARAMETER, paramName, arg.getText(), false)));
usedParamNames.add(paramName);
}
}
else {
final String paramName = generateParameterName(arg, function, usedParamNames, context);
newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(NEW_PARAMETER, paramName, arg.getText(), false)));
usedParamNames.add(paramName);
}
}
return new PyChangeSignatureQuickFix(function, newParameters, mapping.getCallSiteExpression());
return newParameters;
};
return new PyChangeSignatureQuickFix(function, extraParamsSupplier, mapping.getCallSiteExpression());
}
public static @NotNull PyChangeSignatureQuickFix forMismatchingMethods(@NotNull PyFunction function, @NotNull PyFunction complementary) {
final int paramLength = function.getParameterList().getParameters().length;
final int complementaryParamLength = complementary.getParameterList().getParameters().length;
final List<Pair<Integer, PyParameterInfo>> extraParams;
if (complementaryParamLength > paramLength) {
extraParams = Collections.singletonList(Pair.create(paramLength - 1, new PyParameterInfo(NEW_PARAMETER, "**kwargs", "", false)));
}
else {
extraParams = Collections.emptyList();
}
return new PyChangeSignatureQuickFix(function, extraParams, null);
Supplier<List<Pair<Integer, PyParameterInfo>>> extraParamsSupplier = () -> {
final int paramLength = function.getParameterList().getParameters().length;
final int complementaryParamLength = complementary.getParameterList().getParameters().length;
final List<Pair<Integer, PyParameterInfo>> extraParams;
if (complementaryParamLength > paramLength) {
extraParams = Collections.singletonList(Pair.create(paramLength - 1, new PyParameterInfo(NEW_PARAMETER, "**kwargs", "", false)));
}
else {
extraParams = Collections.emptyList();
}
return extraParams;
};
return new PyChangeSignatureQuickFix(function, extraParamsSupplier, null);
}
private final List<Pair<Integer, PyParameterInfo>> myExtraParameters;
private final @NotNull Supplier<List<Pair<Integer, PyParameterInfo>>> myExtraParametersSupplier;
private final @Nullable SmartPsiElementPointer<PyCallSiteExpression> myOriginalCallSiteExpression;
/**
* @param extraParameters new parameters anchored by indexes of the existing parameters they should be inserted <em>after</em>
* (-1 in case they should precede the first parameter)
* @param extraParametersSupplier supplies new parameters anchored by indexes of the existing parameters they should be inserted <em>after</em>
* (-1 in case they should precede the first parameter)
*/
private PyChangeSignatureQuickFix(@NotNull PyFunction function,
@NotNull List<Pair<Integer, PyParameterInfo>> extraParameters,
@NotNull Supplier<List<Pair<Integer, PyParameterInfo>>> extraParametersSupplier,
@Nullable PyCallSiteExpression expression) {
super(function);
myExtraParameters = ContainerUtil.sorted(extraParameters, Comparator.comparingInt(p -> p.getFirst()));
myExtraParametersSupplier = () -> ContainerUtil.sorted(extraParametersSupplier.get(), Comparator.comparingInt(p -> p.getFirst()));
if (expression != null) {
myOriginalCallSiteExpression = SmartPointerManager.getInstance(function.getProject()).createSmartPsiElementPointer(expression);
}
@@ -122,8 +129,12 @@ public final class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement {
if (function == null) {
return getFamilyName();
}
final String params = StringUtil.join(createMethodDescriptor(function).getParameters(), info -> info.isNew() ? PyBundle
.message("QFIX.bold.html.text", info.getName()) : info.getName(), ", ");
List<PyParameterInfo> parameters = new PyMethodDescriptor(function).getParameters();
final String params = StringUtil.join(
parameters,
info -> info.isNew() ? PyBundle.message("QFIX.bold.html.text", info.getName()) : info.getName(),
", "
);
final String message = PyBundle.message("QFIX.change.signature.of", StringUtil.notNullize(function.getName()) + "(" + params + ")");
return XmlStringUtil.wrapInHtml(message);
@@ -198,6 +209,8 @@ public final class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement {
private @NotNull PyMethodDescriptor createMethodDescriptor(final PyFunction function) {
return new PyMethodDescriptor(function) {
private final List<Pair<Integer, PyParameterInfo>> myExtraParameters = myExtraParametersSupplier.get();
@Override
public @NotNull List<PyParameterInfo> getParameters() {
final List<PyParameterInfo> result = new ArrayList<>();

View File

@@ -0,0 +1,37 @@
# bad argument list samples
from m import A, f1, f2, f3, f4
a = A()
a.foo(1,2)
a.bar(<warning descr="Parameter 'two' unfilled">)</warning>;
f1()
f1(<warning descr="Unexpected argument">1</warning>)
f1(<warning descr="Unexpected argument">a = 1</warning>)
f2(<warning descr="Parameter 'a' unfilled">)</warning> # ok, fail
f2(1) # ok, pass
f2(1, <warning descr="Unexpected argument">2</warning>) # ok, fail
f2(a = 1) # ok, pass
f2(<warning descr="Unexpected argument">b = 1</warning><warning descr="Parameter 'a' unfilled">)</warning> # ok, fail
f2(a = 1, <warning descr="Unexpected argument">b = 2</warning>) # ok, fail
f3(1, 2)
f3(1, 2, <warning descr="Unexpected argument">3</warning>)
f3(b=2, a=1)
f3(b=1, <error descr="Keyword argument repeated">b=2</error>, a=1)
f3(1, b=2)
f3(a=1, <error descr="Positional argument after keyword argument">2</error><warning descr="Parameter 'b' unfilled">)</warning>
f4(1)
f4(1, 2)
f4(1, 2, 3)
f4(1, *(2, 3))
f4(*(1,2,3))
f4(a=1, <error descr="Positional argument after keyword argument">2</error>, <error descr="Positional argument after keyword argument">3</error>)

View File

@@ -0,0 +1,23 @@
class A:
def foo(self, x, y):
pass
# no self, but so what
def bar(one, two):
pass
def f1():
pass
def f2(a):
pass
def f3(a, b):
pass
def f4(a, *b):
pass

View File

@@ -749,40 +749,40 @@ public class PyQuickFixTest extends PyTestCase {
// PY-8174
public void testChangeSignatureKeywordAndPositionalParameters() {
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of f(x, foo, <b>bar</b>)</html>", true, true);
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of f(x, foo)</html>", true, true);
}
// PY-8174
public void testChangeSignatureAddKeywordOnlyParameter() {
runWithLanguageLevel(
LanguageLevel.PYTHON34,
() -> doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(x, *args, foo, <b>bar</b>)</html>", true, true)
() -> doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(x, *args, foo)</html>", true, true)
);
}
// PY-8174
public void testChangeSignatureNewParametersNames() {
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(i1, <b>i</b>, <b>i3</b>, <b>num</b>)</html>", true, true);
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(i1)</html>", true, true);
}
// PY-53671
public void testChangeSignatureOfExportedBoundMethod() {
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
doMultiFilesInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of method(self, a, b, <b>i</b>)</html>", "mod.py");
doMultiFilesInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of method(self, a, b)</html>", "mod.py");
});
}
// PY-8174
public void testChangeSignatureParametersDefaultValues() {
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(<b>i</b>, <b>foo</b>)</html>", true, true);
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func()</html>", true, true);
}
public void testAddKwargsToNewMethodIncompatibleWithInit() {
doInspectionTest(PyInitNewSignatureInspection.class, "<html>Change the signature of __new__(cls, <b>**kwargs</b>)</html>", true, true);
doInspectionTest(PyInitNewSignatureInspection.class, "<html>Change the signature of __new__(cls)</html>", true, true);
}
public void testAddKwargsToIncompatibleOverridingMethod() {
doInspectionTest(PyMethodOverridingInspection.class, "<html>Change the signature of m(self, <b>**kwargs</b>)</html>", true, true);
doInspectionTest(PyMethodOverridingInspection.class, "<html>Change the signature of m(self)</html>", true, true);
}
// PY-30789

View File

@@ -18,6 +18,11 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase {
public void testBadarglist() {
doTest();
}
// PY-73886
public void testBadarglistMultiFile() {
doMultiFileTest();
}
public void testKwargsMapToNothing() {
doTest();