mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
PY-73886 PyArgumentListInspection causes file parsing
GitOrigin-RevId: 8fa6b52a2ac5d28e7148c3e10445ee61fef6c89b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d1dd4331f4
commit
5470f70382
@@ -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<>();
|
||||
|
||||
@@ -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>)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -18,6 +18,11 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase {
|
||||
public void testBadarglist() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-73886
|
||||
public void testBadarglistMultiFile() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testKwargsMapToNothing() {
|
||||
doTest();
|
||||
|
||||
Reference in New Issue
Block a user