List<String> (@NotNull PyChangeInfo changeInfo, @NotNull PyCallExpression call) { boolean keywordArgsRequired = false; final List<String> newArguments = new ArrayList<>(); final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(call.getProject(), null); final PyFunction function = changeInfo.getMethod(); final List<PyCallableParameter> allOrigParams = function.getParameters(typeEvalContext); final PyCallExpression.PyArgumentsMapping mapping = PyCallExpressionHelper.mapArguments(call, function, typeEvalContext); MultiMap<Integer, PyExpression> oldParamIndexToArgs = MultiMap.create(); for (Map.Entry<PyExpression, PyCallableParameter> entry : mapping.getMappedParameters().entrySet()) { final PyCallableParameter param = entry.getValue(); oldParamIndexToArgs.putValue(allOrigParams.indexOf(param), entry.getKey()); } assert oldParamIndexToArgs.keySet().stream().allMatch(index -> index >= 0); List<PyParameterInfo> newParamInfos = Arrays.asList(changeInfo.getNewParameters()); final int posVarargIndex = ContainerUtil.indexOf(newParamInfos, info -> isPositionalVarargName(info.getName())); final int posOnlyMarkerIndex = ContainerUtil.indexOf(newParamInfos, info -> PySlashParameter.TEXT.equals(info.getName())); final boolean posVarargEmpty = posVarargIndex != -1 && oldParamIndexToArgs.get(newParamInfos.get(posVarargIndex).getOldIndex()).isEmpty(); List<PyExpression> notInsertedVariadicKeywordArgs = new ArrayList<>(ContainerUtil.filter(call.getArguments(), a -> a instanceof PyStarArgument && ((PyStarArgument)a).isKeyword())); boolean variadicKeywordArgsUsed = false; final int implicitCount = mapping.getImplicitParameters().size(); for (int paramIndex = implicitCount; paramIndex < newParamInfos.size(); paramIndex++) { PyParameterInfo info = newParamInfos.get(paramIndex); final String paramName = info.getName(); final boolean isKeywordVararg = isKeywordVarargName(paramName); final boolean isPositionalVararg = isPositionalVarargName(paramName); final boolean beforePositionalOnlyMarker = paramIndex < posOnlyMarkerIndex; final boolean defaultShouldBeInlined = beforePositionalOnlyMarker && ContainerUtil.exists(newParamInfos.subList(paramIndex + 1, posOnlyMarkerIndex), i -> !i.isNew() && !oldParamIndexToArgs.get(i.getOldIndex()).isEmpty()); if (paramName.equals(PySingleStarParameter.TEXT)) { keywordArgsRequired = true; continue; } if (paramName.equals(PySlashParameter.TEXT)) { continue; } final String paramDefault = StringUtil.notNullize(info.getDefaultValue()); final int oldIndex = info.getOldIndex(); if (oldIndex < 0) { if (info.getDefaultInSignature() && !defaultShouldBeInlined) { // If the next argument was passed by position it would match with this new default. // Imagine "def f(x, y=None): ..." -> "def f(x, foo=None, y=None): ..." and a call "f(1, 2)" keywordArgsRequired = true; } else if (!isKeywordVararg && !isPositionalVararg) { newArguments.add(formatArgument(paramName, paramDefault, keywordArgsRequired)); } } else { final Collection<PyExpression> existingArgs = oldParamIndexToArgs.get(oldIndex); final PyCallableParameter oldParam = allOrigParams.get(oldIndex); final boolean usesValueFromVariadic = mapping.getParametersMappedToVariadicKeywordArguments().contains(oldParam); variadicKeywordArgsUsed |= usesValueFromVariadic; if (!existingArgs.isEmpty()) { for (PyExpression arg : existingArgs) { PyExpression argValue; String argName; if (arg instanceof PyKeywordArgument) { argValue = ((PyKeywordArgument)arg).getValueExpression(); argName = StringUtil.notNullize(((PyKeywordArgument)arg).getKeyword()); } else { argValue = arg; argName = ""; } notInsertedVariadicKeywordArgs.remove(argValue); // Keep format of existing keyword arguments unless it's illegal in their new position if (!argName.isEmpty() && !(paramIndex < posVarargIndex && !posVarargEmpty) && !beforePositionalOnlyMarker) { keywordArgsRequired = true; } assert !(isPositionalVararg && keywordArgsRequired); final String argValueText = argValue != null ? argValue.getText() : ""; final String newArgumentName = isKeywordVararg ? argName : paramName; newArguments.add(formatArgument(newArgumentName, argValueText, keywordArgsRequired)); } } // Parameter receives its default value from the signature, all subsequent arguments must use keyword form else if (info.getDefaultInSignature() && !defaultShouldBeInlined) { keywordArgsRequired = true; } else if (!isPositionalVararg && !isKeywordVararg && !usesValueFromVariadic) { // Existing ordinary parameter with neither a default value in the signature, not a corresponding argument. // Most likely its default was propagated from the signature down to calls. newArguments.add(formatArgument(paramName, paramDefault, keywordArgsRequired)); } } // Keyword-only arguments should follow (Python 3) if (isPositionalVararg) { keywordArgsRequired = true; } } if (variadicKeywordArgsUsed) { newArguments.addAll(ContainerUtil.map(notInsertedVariadicKeywordArgs, PsiElement::getText)); } return newArguments; }