mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
PY-1268, PY-2005, a bunch of fixes to the broken logic of parameter mapper.
This commit is contained in:
@@ -128,6 +128,9 @@ INSP.cannot.appear.past.keyword.arg=Cannot appear past keyword arguments or *arg
|
||||
INSP.unexpected.arg=Unexpected argument
|
||||
INSP.parameter.$0.unfilled=Parameter ''{0}'' unfilled
|
||||
INSP.func.$0.lacks.first.arg=Function ''{0}'' lacks a positional argument
|
||||
INSP.expected.dict.got.$0=Expected a dictionary, got {0}
|
||||
INSP.expected.seq.got.$0=Expected a sequence, got {0}
|
||||
INSP.more.args.that.pos.params=More arguments than positional parameters left
|
||||
INSP.cannot.analyze=This argument list cannot be analyzed
|
||||
|
||||
# PyMethodParametersInspection
|
||||
|
||||
@@ -34,7 +34,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
|
||||
public PyArgumentList findElementForParameterInfo(final CreateParameterInfoContext context) {
|
||||
PyArgumentList arglist = findArgumentList(context);
|
||||
if (arglist != null) {
|
||||
PyArgumentList.AnalysisResult result = arglist.analyzeCall(TypeEvalContext.fast());
|
||||
PyArgumentList.AnalysisResult result = arglist.analyzeCall(TypeEvalContext.slow());
|
||||
if (result.getMarkedCallee() != null && !result.isImplicitlyResolved()) {
|
||||
context.setItemsToShow(new Object[] { result });
|
||||
return arglist;
|
||||
@@ -66,7 +66,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
|
||||
return;
|
||||
}
|
||||
// align offset to nearest expression; context may point to a space, etc.
|
||||
List<PyExpression> flat_args = PyUtil.flattenedParens(arglist.getArguments());
|
||||
List<PyExpression> flat_args = PyUtil.flattenedParensAndLists(arglist.getArguments());
|
||||
int alleged_cursor_offset = context.getOffset(); // this is already shifted backwards to skip spaces
|
||||
PsiFile file = context.getFile();
|
||||
CharSequence chars = file.getViewProvider().getContents();
|
||||
@@ -153,7 +153,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
|
||||
}
|
||||
|
||||
// highlight current param(s)
|
||||
final List<PyExpression> args = PyUtil.flattenedParens(arglist.getArguments());
|
||||
final List<PyExpression> args = PyUtil.flattenedParensAndLists(arglist.getArguments());
|
||||
for (PyExpression arg : args) {
|
||||
if (arg.getTextRange().contains(current_param_offset)) {
|
||||
PyNamedParameter param = result.getPlainMappedParams().get(arg);
|
||||
|
||||
@@ -37,7 +37,7 @@ public class PyFunctionInsertHandler extends ParenthesesInsertHandler<LookupElem
|
||||
final PsiElement element = context.getFile().findElementAt(context.getStartOffset());
|
||||
PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
|
||||
int implicitArgsCount = refExpr != null
|
||||
? PyCallExpressionHelper.getImplicitArgumentCount(refExpr, function)
|
||||
? PyCallExpressionHelper.getImplicitArgumentCount(refExpr, function, null)
|
||||
: 0;
|
||||
return function.getParameterList().getParameters().length > implicitArgsCount;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public class PyArgumentListInspection extends PyInspection {
|
||||
holder.registerProblem(arg, PyBundle.message("INSP.unexpected.arg"));
|
||||
}
|
||||
if (flags.contains(PyArgumentList.ArgFlag.IS_TOO_LONG)) {
|
||||
holder.registerProblem(arg, "More arguments than positional parameters left");
|
||||
holder.registerProblem(arg, PyBundle.message("INSP.more.args.that.pos.params"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,12 +133,12 @@ public class PyArgumentListInspection extends PyInspection {
|
||||
if (inside_type != null) {
|
||||
if (((PyStarArgument)arg).isKeyword()) {
|
||||
if (! isMappingType(inside_type)) {
|
||||
holder.registerProblem(arg, "Expected a dictionary, got " + inside_type.getName());
|
||||
holder.registerProblem(arg, PyBundle.message("INSP.expected.dict.got.$0", inside_type.getName()));
|
||||
}
|
||||
}
|
||||
else { // * arg
|
||||
if (! isSequenceType(inside_type)) {
|
||||
holder.registerProblem(arg, "Expected a sequence, got " + inside_type.getName());
|
||||
holder.registerProblem(arg, PyBundle.message("INSP.expected.seq.got.$0", inside_type.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
|
||||
LanguageLevel level;
|
||||
if (psifile instanceof PyFile) level = ((PyFile)psifile).getLanguageLevel();
|
||||
else level = LanguageLevel.PYTHON24; // lowest common
|
||||
ret.mapArguments2(arguments, resolved_callee, level);
|
||||
ret.mapArguments(arguments, resolved_callee, context);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -26,11 +26,15 @@ public class PyCallExpressionHelper {
|
||||
// none
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an argument to the end of argument list.
|
||||
* @param us the arg list
|
||||
* @param expression what to add
|
||||
*/
|
||||
public static void addArgument(PyCallExpression us, PyExpression expression) {
|
||||
PyExpression[] arguments = us.getArgumentList().getArguments();
|
||||
PyElementGenerator.getInstance(us.getProject()).insertItemIntoList(us,
|
||||
arguments.length == 0 ? null : arguments[arguments.length - 1],
|
||||
expression);
|
||||
final PyExpression last_arg = arguments.length == 0 ? null : arguments[arguments.length - 1];
|
||||
PyElementGenerator.getInstance(us.getProject()).insertItemIntoList(us, last_arg, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,16 +143,14 @@ public class PyCallExpressionHelper {
|
||||
if (resolved instanceof Callable) {
|
||||
EnumSet<PyFunction.Flag> flags = EnumSet.noneOf(PyFunction.Flag.class);
|
||||
PyExpression lastQualifier = resolveResult != null ? resolveResult.getLastQualifier() : null;
|
||||
final PyExpression callReference = us.getCallee();
|
||||
boolean is_by_instance = isConstructorCall || /*isByInstance(callReference, context);*/
|
||||
divinate(us.getCallee(), (Callable)resolved, lastQualifier, context);
|
||||
boolean is_by_instance = isConstructorCall || isQualifiedByInstance((Callable)resolved, lastQualifier, context);
|
||||
if (lastQualifier != null) {
|
||||
PyType qualifier_type = context.getType(lastQualifier);
|
||||
is_by_instance |=
|
||||
(qualifier_type != null && qualifier_type instanceof PyClassType && !((PyClassType)qualifier_type).isDefinition());
|
||||
}
|
||||
final Callable callable = (Callable)resolved;
|
||||
int implicitOffset = getImplicitArgumentCount(callReference, callable, wrappedFlag, flags, is_by_instance);
|
||||
int implicitOffset = getImplicitArgumentCount(callable, wrappedFlag, flags, is_by_instance);
|
||||
if (!isConstructorCall && PyNames.NEW.equals(callable.getName())) {
|
||||
implicitOffset = Math.min(implicitOffset - 1, 0); // case of Class.__new__
|
||||
}
|
||||
@@ -164,28 +166,35 @@ public class PyCallExpressionHelper {
|
||||
*
|
||||
* @param callReference the call site, where arguments are given.
|
||||
* @param functionBeingCalled resolved method which is being called; plain functions are OK but make little sense.
|
||||
* @param typeContext shared type evaluation context
|
||||
* @return a non-negative number of parameters that are implicit to this call.
|
||||
*/
|
||||
public static int getImplicitArgumentCount(final PyExpression callReference, PyFunction functionBeingCalled) {
|
||||
return getImplicitArgumentCount(callReference, functionBeingCalled, null, null, isByInstance(callReference, TypeEvalContext.fast()));
|
||||
public static int getImplicitArgumentCount(
|
||||
@NotNull final PyReferenceExpression callReference,
|
||||
@NotNull PyFunction functionBeingCalled,
|
||||
@Nullable TypeEvalContext typeContext
|
||||
) {
|
||||
//return getImplicitArgumentCount(functionBeingCalled, null, null, qualifierIsAnInstance(callReference, TypeEvalContext.fast()));
|
||||
if (typeContext == null) typeContext = TypeEvalContext.fast();
|
||||
QualifiedResolveResult followed = callReference.followAssignmentsChain(typeContext);
|
||||
return getImplicitArgumentCount(functionBeingCalled, null, null, isQualifiedByInstance(functionBeingCalled, followed.getLastQualifier(), typeContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds how many arguments are implicit in a given call.
|
||||
*
|
||||
* @param callReference the call site, where arguments are given.
|
||||
* @param callable resolved method which is being called; other callables are OK but immediately return 0.
|
||||
* @param wrappedFlag value of {@link PyFunction.Flag#WRAPPED} if known.
|
||||
* @param callable resolved method which is being called; non-methods immediately return 0.
|
||||
* @param wrappedFlag value of {@link com.jetbrains.python.psi.PyFunction.Flag#WRAPPED} if known.
|
||||
* @param flags set of flags to be <i>updated</i> by this call; wrappedFlag's value ends up here, too.
|
||||
* @param isByInstance true if the call is known to be by instance (not by class).
|
||||
* @return a non-negative number of parameters that are implicit to this call. E.g. for a typical method call 1 is returned
|
||||
* because one parameter ('self') is implicit.
|
||||
*/
|
||||
private static int getImplicitArgumentCount(final PyExpression callReference,
|
||||
Callable callable,
|
||||
@Nullable PyFunction.Flag wrappedFlag,
|
||||
@Nullable EnumSet<PyFunction.Flag> flags,
|
||||
boolean isByInstance
|
||||
private static int getImplicitArgumentCount(
|
||||
Callable callable,
|
||||
@Nullable PyFunction.Flag wrappedFlag,
|
||||
@Nullable EnumSet<PyFunction.Flag> flags,
|
||||
boolean isByInstance
|
||||
) {
|
||||
int implicit_offset = 0;
|
||||
PyFunction method = callable.asMethod();
|
||||
@@ -206,21 +215,11 @@ public class PyCallExpressionHelper {
|
||||
} // Both Foo.method() and foo.method() have implicit the first arg
|
||||
}
|
||||
if (!isByInstance && PyNames.NEW.equals(method.getName())) implicit_offset += 1; // __new__ call
|
||||
/*
|
||||
if (PyNames.INIT.equals(method.getName())) {
|
||||
String refName = callReference instanceof PyReferenceExpression
|
||||
? ((PyReferenceExpression)callReference).getReferencedName()
|
||||
: null;
|
||||
if (!PyNames.INIT.equals(refName)) { // PY-312
|
||||
implicit_offset += 1;
|
||||
}
|
||||
}
|
||||
*/
|
||||
// decorators?
|
||||
// look for closest decorator
|
||||
PyDecoratorList decolist = method.getDecoratorList();
|
||||
if (decolist != null) {
|
||||
PyDecorator[] decos = decolist.getDecorators();
|
||||
// look for closest decorator
|
||||
// TODO: look for all decorators
|
||||
if (decos.length == 1) {
|
||||
PyDecorator deco = decos[0];
|
||||
@@ -244,21 +243,7 @@ public class PyCallExpressionHelper {
|
||||
return implicit_offset;
|
||||
}
|
||||
|
||||
protected static boolean isByInstance(final PyExpression callee, TypeEvalContext context) {
|
||||
if (callee instanceof PyReferenceExpression) {
|
||||
PyExpression qualifier = ((PyReferenceExpression)callee).getQualifier();
|
||||
if (qualifier != null) {
|
||||
PyType type = context.getType(qualifier);
|
||||
if ((type instanceof PyClassType) && (!((PyClassType)type).isDefinition())) {
|
||||
// we're calling an instance method of qualifier
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean divinate(PyExpression callee, Callable resolved, PyExpression lastQualifier, TypeEvalContext context) {
|
||||
private static boolean isQualifiedByInstance(Callable resolved, PyExpression lastQualifier, TypeEvalContext context) {
|
||||
// true = call by instance
|
||||
PyFunction method = resolved.asMethod();
|
||||
if (method != null) {
|
||||
@@ -325,9 +310,14 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
final PyParameter[] nested_params = param.getContents();
|
||||
if (elements != null) { // recursively map expression's tuple to parameter's.
|
||||
MyParamVisitor visitor = new MyParamVisitor(Arrays.asList(elements).iterator(), myResult);
|
||||
final Iterator<PyExpression> subargs_iterator = Arrays.asList(elements).iterator();
|
||||
MyParamVisitor visitor = new MyParamVisitor(subargs_iterator, myResult);
|
||||
for (PyParameter nested : nested_params) nested.accept(visitor);
|
||||
myUnmatchedSubargs.addAll(visitor.getUnmatchedSubargs());
|
||||
while (subargs_iterator.hasNext()) { // more args in a tuple than parameters
|
||||
PyExpression overflown_arg = subargs_iterator.next();
|
||||
myResult.markArgument(overflown_arg, PyArgumentList.ArgFlag.IS_UNMAPPED);
|
||||
}
|
||||
}
|
||||
else { // map all what's inside to this arg
|
||||
final List<PyNamedParameter> nested_mapped = new ArrayList<PyNamedParameter>(nested_params.length);
|
||||
@@ -385,312 +375,10 @@ public class PyCallExpressionHelper {
|
||||
* must contain already resolved callee with flags set appropriately
|
||||
* @param arguments what to map, get if from call site
|
||||
* @param resolved_callee
|
||||
* @param type_context
|
||||
*/
|
||||
void mapArguments(PyExpression[] arguments, PyCallExpression.PyMarkedCallee resolved_callee, LanguageLevel language_level) {
|
||||
myMarkedCallee = resolved_callee;
|
||||
Callable callable = resolved_callee.getCallable();
|
||||
PyParameterList paramlist = callable.getParameterList();
|
||||
PyParameter[] params = paramlist.getParameters();
|
||||
// prepare args and slots
|
||||
List<PyExpression> unmatched_args = new LinkedList<PyExpression>();
|
||||
Collections.addAll(unmatched_args, arguments);
|
||||
Map<String, PyExpression> param_slots = new HashMap<String, PyExpression>();
|
||||
PyNamedParameter kwd_slot = null; // the **kwd
|
||||
PyNamedParameter tuple_slot = null; // the *tuple. might be just boolean, but this way debugging is easier
|
||||
PyStarArgument kwd_arg = null;
|
||||
PyStarArgument tuple_arg = null;
|
||||
final List<PyExpression> unmatched_subargs = new LinkedList<PyExpression>(); // unmatched nested arguments will go here
|
||||
// all slots are initially empty, *x and **x are not among slots
|
||||
for (PyParameter a_param : params) {
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) {
|
||||
if (n_param.isPositionalContainer()) tuple_slot = n_param;
|
||||
else if (n_param.isKeywordContainer()) kwd_slot = n_param;
|
||||
else param_slots.put(a_param.getName(), null);
|
||||
}
|
||||
}
|
||||
// look for star args, mark duplicate star args
|
||||
for (PyExpression arg : arguments) {
|
||||
if (arg instanceof PyStarArgument) {
|
||||
final PyStarArgument star_arg = (PyStarArgument)arg;
|
||||
if (star_arg.isKeyword()) {
|
||||
if (kwd_arg == null) kwd_arg = star_arg;
|
||||
else {
|
||||
markArgument(arg, PyArgumentList.ArgFlag.IS_DUP_KWD);
|
||||
unmatched_args.remove(arg); // error. ignore later
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tuple_arg == null) {
|
||||
tuple_arg = star_arg;
|
||||
}
|
||||
else {
|
||||
markArgument(arg, PyArgumentList.ArgFlag.IS_DUP_TUPLE);
|
||||
unmatched_args.remove(arg); // error. ignore later
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// rule out 'self' or other implicit params
|
||||
int param_index = 0;
|
||||
for (int i=0; i < resolved_callee.getImplicitOffset() && i < params.length; i+=1) {
|
||||
param_slots.remove(params[i].getName());
|
||||
param_index += 1;
|
||||
}
|
||||
boolean seen_tuple_arg = false;
|
||||
boolean seen_kwd_arg = false;
|
||||
boolean seenSingleStar = false;
|
||||
ListIterator<PyExpression> unmatched_arg_iter = unmatched_args.listIterator();
|
||||
// check positional args
|
||||
while (unmatched_arg_iter.hasNext() && (param_index < params.length)) {
|
||||
PyParameter a_param = params[param_index]; // its matching param
|
||||
if (a_param instanceof PySingleStarParameter) {
|
||||
param_index++;
|
||||
seenSingleStar = true;
|
||||
continue;
|
||||
}
|
||||
final PyExpression arg = unmatched_arg_iter.next(); // current arg
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) { // named
|
||||
if (
|
||||
arg instanceof PyKeywordArgument || arg instanceof PyStarArgument ||
|
||||
n_param.isKeywordContainer() || n_param.isPositionalContainer()
|
||||
) {
|
||||
seen_tuple_arg |= (arg == tuple_arg);
|
||||
seen_kwd_arg |= (arg == kwd_arg);
|
||||
unmatched_arg_iter.previous(); // step back
|
||||
break;
|
||||
}
|
||||
if (!seenSingleStar) {
|
||||
param_slots.put(n_param.getName(), arg); // it cannot yet contain this name unless function definition is broken
|
||||
myPlainMappedParams.put(arg, n_param);
|
||||
}
|
||||
else {
|
||||
param_index++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else { // tuple: it may contain only positionals or other tuples.
|
||||
PyTupleParameter tupleParameter = a_param.getAsTuple();
|
||||
if (tupleParameter != null) {
|
||||
unmatched_arg_iter.previous(); // step back so that the visitor takes this arg again
|
||||
MyParamVisitor visitor = new MyParamVisitor(unmatched_arg_iter, this);
|
||||
visitor.enterTuple(a_param.getAsTuple()); // will recur as needed
|
||||
unmatched_subargs.addAll(visitor.getUnmatchedSubargs()); // what it's seen
|
||||
}
|
||||
}
|
||||
unmatched_arg_iter.remove(); // it has been matched
|
||||
param_index += 1;
|
||||
}
|
||||
if (!seen_kwd_arg) { // **kwd arg is the last; if it's present, checking the rest would be useless
|
||||
if (!seen_tuple_arg) { // any pos args can only come before *arg
|
||||
// some pos args might go to a *param
|
||||
if (tuple_slot != null) {
|
||||
while (unmatched_arg_iter.hasNext()) {
|
||||
PyExpression arg = unmatched_arg_iter.next();
|
||||
if (arg instanceof PyKeywordArgument) {
|
||||
unmatched_arg_iter.previous(); // step back
|
||||
break;
|
||||
}
|
||||
myPlainMappedParams.put(arg, tuple_slot);
|
||||
unmatched_arg_iter.remove(); // consumed as nameless
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tuple_slot != null) {
|
||||
// if we have both *param and *arg, any parameters up to *param are mapped to *arg;
|
||||
// syntax requires that any args before *arg can only be positional, for named params or not.
|
||||
for (int i=0; params[i] != tuple_slot; i+=1) {
|
||||
if (params[i] instanceof PyNamedParameter) {
|
||||
PyNamedParameter param = (PyNamedParameter)params[i];
|
||||
myPlainMappedParams.put(tuple_arg, param);
|
||||
param_slots.put(param.getName(), tuple_arg);
|
||||
}
|
||||
// else: a non-named param is either *param or **param; further checks will handle these
|
||||
}
|
||||
}
|
||||
// check named args
|
||||
boolean seen_kwd = false;
|
||||
while (unmatched_arg_iter.hasNext()) {
|
||||
PyExpression arg = unmatched_arg_iter.next();
|
||||
if (arg instanceof PyKeywordArgument) {
|
||||
if (!seen_kwd_arg && (!seen_tuple_arg || language_level.isAtLeast(LanguageLevel.PYTHON30))) {
|
||||
final String argname = ((PyKeywordArgument)arg).getKeyword();
|
||||
if (param_slots.containsKey(argname)) { // slot is known
|
||||
if (param_slots.get(argname) == null) { // slot is not filled
|
||||
param_slots.put(argname, arg);
|
||||
// we'll put() it to ret.myPlainMappedParams later
|
||||
seen_kwd = true;
|
||||
}
|
||||
else markArgument(arg, PyArgumentList.ArgFlag.IS_DUP);
|
||||
unmatched_arg_iter.remove(); // it has been matched or flagged, forget
|
||||
}
|
||||
// else: ignore unknown arg, we'll deal with them later
|
||||
}
|
||||
else {
|
||||
markArgument(arg, PyArgumentList.ArgFlag.IS_UNMAPPED);
|
||||
unmatched_arg_iter.remove(); // it has been flagged, forget
|
||||
}
|
||||
}
|
||||
else if (seen_kwd && (arg != kwd_arg) && (arg != tuple_arg)) {
|
||||
markArgument(arg, PyArgumentList.ArgFlag.IS_POS_PAST_KWD);
|
||||
unmatched_arg_iter.remove(); // it has been flagged, forget
|
||||
}
|
||||
seen_tuple_arg |= (arg == tuple_arg);
|
||||
seen_kwd_arg |= (arg == kwd_arg);
|
||||
}
|
||||
// some named args might go to a **kwd param
|
||||
if (kwd_slot != null) {
|
||||
unmatched_arg_iter = unmatched_args.listIterator(); // anew
|
||||
while (unmatched_arg_iter.hasNext()) {
|
||||
PyExpression arg = unmatched_arg_iter.next();
|
||||
if (arg instanceof PyKeywordArgument) {
|
||||
myPlainMappedParams.put(arg, kwd_slot);
|
||||
unmatched_arg_iter.remove(); // consumed by **kwd
|
||||
}
|
||||
// no else: name errors are all detected above
|
||||
}
|
||||
}
|
||||
}
|
||||
if (seen_tuple_arg) { // link remaining params to *arg if present
|
||||
for (PyParameter a_param : params) {
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) {
|
||||
final String param_name = n_param.getName();
|
||||
if (
|
||||
(!n_param.hasDefaultValue()) && // has no default value
|
||||
param_slots.containsKey(param_name) && // known as a slot
|
||||
(param_slots.get(param_name) == null) // the slot yet unfilled
|
||||
) {
|
||||
param_slots.put(param_name, tuple_arg);
|
||||
unmatched_args.remove(tuple_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (seen_kwd_arg) { // link remaining params to **kwarg if present
|
||||
for (PyParameter a_param : params) {
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) {
|
||||
final String param_name = n_param.getName();
|
||||
if (
|
||||
(!n_param.hasDefaultValue()) && // has no default value
|
||||
param_slots.containsKey(param_name) && // known as a slot
|
||||
(param_slots.get(param_name) == null) // the slot yet unfilled
|
||||
) {
|
||||
param_slots.put(param_name, kwd_arg);
|
||||
unmatched_args.remove(kwd_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check and collect all yet unfilled params without default values
|
||||
Map<String, PyNamedParameter> unfilled_params = new HashMap<String, PyNamedParameter>();
|
||||
for (PyParameter a_param : params) {
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) {
|
||||
final String param_name = n_param.getName();
|
||||
if (
|
||||
(!n_param.hasDefaultValue()) && // has no default value
|
||||
param_slots.containsKey(param_name) && // known as a slot
|
||||
(param_slots.get(param_name) == null) // the slot yet unfilled
|
||||
) {
|
||||
if (tuple_arg != null) {
|
||||
// An *arg, if present, fills all positional params
|
||||
param_slots.put(param_name, tuple_arg);
|
||||
unmatched_args.remove(tuple_arg);
|
||||
}
|
||||
else {
|
||||
unfilled_params.put(param_name, n_param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// *arg and **kwarg are not in slots list; write any *param or **param off to them if present.
|
||||
if (kwd_arg != null && kwd_slot != null) {
|
||||
myKwdMappedParams.add(kwd_slot);
|
||||
unmatched_args.remove(kwd_arg);
|
||||
}
|
||||
if (tuple_arg != null && tuple_slot != null) {
|
||||
myTupleMappedParams.add(tuple_slot);
|
||||
unmatched_args.remove(tuple_arg);
|
||||
}
|
||||
// maybe we did not map a star arg because all eligible params have defaults; time to be less picky now.
|
||||
if (tuple_arg != null && myTupleMappedParams.isEmpty()) { // link remaining params to *arg if nothing else is mapped to it
|
||||
for (PyParameter a_param : params) {
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) {
|
||||
final String param_name = n_param.getName();
|
||||
if (
|
||||
param_slots.containsKey(param_name) && // known as a slot
|
||||
(param_slots.get(param_name) == null) // the slot yet unfilled
|
||||
) {
|
||||
param_slots.put(param_name, tuple_arg);
|
||||
unmatched_args.remove(tuple_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kwd_arg != null && myKwdMappedParams.isEmpty()) { // link remaining params to **kwarg if nothing else is mapped to it
|
||||
for (PyParameter a_param : params) {
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) {
|
||||
final String param_name = n_param.getName();
|
||||
if (
|
||||
param_slots.containsKey(param_name) && // known as a slot
|
||||
(param_slots.get(param_name) == null) // the slot yet unfilled
|
||||
) {
|
||||
param_slots.put(param_name, kwd_arg);
|
||||
unmatched_args.remove(kwd_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// any args left?
|
||||
boolean tuple_arg_consumed_some = false;
|
||||
for (PyExpression arg : param_slots.values()) { // any(\x: x == tuple_arg)
|
||||
if (arg != null && arg == tuple_arg) {
|
||||
tuple_arg_consumed_some = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (PyExpression arg : unmatched_args) {
|
||||
//getHolder().createErrorAnnotation(arg, "unexpected arg");
|
||||
if (arg == kwd_arg && tuple_arg_consumed_some) continue; // *arg consumed anything that **arg might equally consume.
|
||||
markArgument(arg, PyArgumentList.ArgFlag.IS_UNMAPPED);
|
||||
}
|
||||
// any params still unfilled?
|
||||
for (final PyNamedParameter param : unfilled_params.values()) {
|
||||
// getHolder().createErrorAnnotation(close_paren, "parameter '" + param_name + "' unfilled");
|
||||
myUnmappedParams.add(param);
|
||||
}
|
||||
// copy the mapping of args
|
||||
for (PyParameter a_param : params) {
|
||||
PyNamedParameter n_param = a_param.getAsNamed();
|
||||
if (n_param != null) {
|
||||
PyExpression arg = param_slots.get(n_param.getName());
|
||||
if (arg != null) {
|
||||
if (arg instanceof PyStarArgument) {
|
||||
PyStarArgument star_arg = (PyStarArgument)arg;
|
||||
if (star_arg.isKeyword()) myKwdMappedParams.add(n_param);
|
||||
else myTupleMappedParams.add(n_param);
|
||||
}
|
||||
else myPlainMappedParams.put(arg, n_param);
|
||||
}
|
||||
}
|
||||
}
|
||||
// copy starred args
|
||||
myKwdArg = kwd_arg;
|
||||
myTupleArg = tuple_arg;
|
||||
// add unmatched nested arguments
|
||||
for (PyExpression subarg : unmatched_subargs) {
|
||||
myArgFlags.put(subarg, EnumSet.of(PyArgumentList.ArgFlag.IS_UNMAPPED));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void mapArguments2(PyExpression[] arguments, PyCallExpression.PyMarkedCallee resolved_callee, LanguageLevel language_level) {
|
||||
TypeEvalContext type_context = TypeEvalContext.fast(); // TODO: get it from parameters
|
||||
void mapArguments(PyExpression[] arguments, PyCallExpression.PyMarkedCallee resolved_callee, TypeEvalContext type_context) {
|
||||
if (type_context == null) type_context = TypeEvalContext.fast();
|
||||
myMarkedCallee = resolved_callee;
|
||||
List<PyExpression> unmatched_args = new LinkedList<PyExpression>();
|
||||
Collections.addAll(unmatched_args, arguments);
|
||||
@@ -777,6 +465,13 @@ public class PyCallExpressionHelper {
|
||||
if (arg instanceof PyParenthesizedExpression) {
|
||||
mapped_args.add(arg); // tuple itself is always mapped; its insides can fail
|
||||
}
|
||||
else {
|
||||
PyType arg_type = type_context.getType(arg);
|
||||
if (arg_type != null && arg_type.isBuiltin() && "list".equals(arg_type.getName())) {
|
||||
mapped_args.add(arg); // we can't really analyze arbitrary lists statically yet
|
||||
// but ListLiteralExpressions are handled by visitor
|
||||
}
|
||||
}
|
||||
unmatched_arg_iter.previous();
|
||||
MyParamVisitor visitor = new MyParamVisitor(unmatched_arg_iter, this);
|
||||
visitor.enterTuple(t_par.getAsTuple()); // will recur as needed
|
||||
@@ -788,7 +483,7 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
else break;
|
||||
}
|
||||
// anything left after mapping of tuple params?
|
||||
// anything left after mapping of nested-tuple params?
|
||||
for (Map.Entry<PyExpression, List<PyNamedParameter>> pair : myNestedMappedParams.entrySet()) {
|
||||
PyExpression arg = pair.getKey();
|
||||
List<PyNamedParameter> params = pair.getValue();
|
||||
@@ -807,6 +502,7 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
boolean seen_named_args = false;
|
||||
// map named args to named params if possible
|
||||
Map<String, PyNamedParameter> parameter_by_name = new HashMap<String, PyNamedParameter>();
|
||||
for (PyParameter par : parameters) {
|
||||
@@ -827,9 +523,11 @@ public class PyCallExpressionHelper {
|
||||
mapped_args.add(arg);
|
||||
}
|
||||
}
|
||||
seen_named_args = true;
|
||||
}
|
||||
}
|
||||
// map *arg to positional params if possible
|
||||
boolean tuple_arg_not_exhausted = false;
|
||||
if (cnt < parameters.length && cnt < positional_index && myTupleArg != null) {
|
||||
// check length of myTupleArg
|
||||
PyType tuple_arg_type = null;
|
||||
@@ -851,14 +549,6 @@ public class PyCallExpressionHelper {
|
||||
final PyExpression arg_here = slots.get(n_par);
|
||||
final boolean over_tuple_length = tuple_length_known && i > tuple_length;
|
||||
if (over_tuple_length || arg_here != null) {
|
||||
/*
|
||||
if (!over_tuple_length && arg_here != null) {
|
||||
// tuple would overwrite these
|
||||
markArgument(arg_here, PyArgumentList.ArgFlag.IS_DUP);
|
||||
myTupleMappedParams.add(n_par);
|
||||
mapped_args.add(myTupleArg);
|
||||
}
|
||||
*/
|
||||
// the spree is over
|
||||
break;
|
||||
}
|
||||
@@ -872,7 +562,7 @@ public class PyCallExpressionHelper {
|
||||
i += 1;
|
||||
}
|
||||
if (tuple_length_known && i <= tuple_length) {
|
||||
markArgument(myTupleArg, PyArgumentList.ArgFlag.IS_TOO_LONG);
|
||||
tuple_arg_not_exhausted = true;
|
||||
}
|
||||
}
|
||||
// map *param to the leftmost chunk of unmapped positional args
|
||||
@@ -891,10 +581,19 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
}
|
||||
// map unmapped *arg to *param
|
||||
if (myTupleArg != null && !mapped_args.contains(myTupleArg) && tuple_par != null) {
|
||||
myTupleMappedParams.add(tuple_par);
|
||||
mapped_args.add(myTupleArg);
|
||||
if (myTupleArg != null && tuple_par != null) {
|
||||
if (!mapped_args.contains(myTupleArg)) {
|
||||
myTupleMappedParams.add(tuple_par);
|
||||
mapped_args.add(myTupleArg);
|
||||
}
|
||||
else if (! seen_named_args && tuple_arg_not_exhausted) {
|
||||
// case of (*(1, 2, 3)) -> (a, *b); map the rest of *arg to *param
|
||||
myTupleMappedParams.add(tuple_par);
|
||||
mapped_args.add(myTupleArg);
|
||||
tuple_arg_not_exhausted = false;
|
||||
}
|
||||
}
|
||||
if (tuple_arg_not_exhausted) markArgument(myTupleArg, PyArgumentList.ArgFlag.IS_TOO_LONG);
|
||||
// map unmapped named params to **kwarg
|
||||
if (myKwdArg != null) {
|
||||
for (PyParameter par : parameters) {
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>45</line>
|
||||
<description>Cannot appear past keyword arguments</description>
|
||||
<description>Cannot appear past keyword arguments or *arg or **kwarg</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
@@ -63,12 +63,12 @@
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>55</line>
|
||||
<description>Cannot appear past keyword arguments</description>
|
||||
<description>Cannot appear past keyword arguments or *arg or **kwarg</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>55</line> <!-- yes, two identical problems on same line -->
|
||||
<description>Cannot appear past keyword arguments</description>
|
||||
<description>Cannot appear past keyword arguments or *arg or **kwarg</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
@@ -120,9 +120,74 @@
|
||||
<line>132</line>
|
||||
<description>Parameter 'p2' unfilled</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>145</line>
|
||||
<description>Unexpected argument</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>158</line>
|
||||
<description>Function 'baddeco' lacks a positional argument</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>172</line>
|
||||
<description>This argument list cannot be analyzed</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>211</line>
|
||||
<description>More arguments than positional parameters left</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>212</line>
|
||||
<description>More arguments than positional parameters left</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>213</line>
|
||||
<description>Expected a sequence, got int</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>214</line>
|
||||
<description>Parameter 'c' unfilled</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>227</line>
|
||||
<description>Cannot appear past keyword arguments or *arg or **kwarg</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>232</line>
|
||||
<description>More arguments than positional parameters left</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>233</line>
|
||||
<description>More arguments than positional parameters left</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>236</line>
|
||||
<description>More arguments than positional parameters left</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>237</line>
|
||||
<description>More arguments than positional parameters left</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>238</line>
|
||||
<description>Unexpected argument</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>badarglist.py</file>
|
||||
<line>243</line>
|
||||
<description>More arguments than positional parameters left</description>
|
||||
</problem>
|
||||
</problems>
|
||||
|
||||
10
python/testData/paramInfo/ConstructorFactory.py
Normal file
10
python/testData/paramInfo/ConstructorFactory.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class Foo(object):
|
||||
def __init__(self, color):
|
||||
self.color = color
|
||||
|
||||
class Bar(object):
|
||||
fooFactory = Foo
|
||||
|
||||
def quux(self):
|
||||
foo = self.fooFactory(<arg>"orange") #
|
||||
|
||||
4
python/testData/paramInfo/TupleAndNamedArg1.py
Normal file
4
python/testData/paramInfo/TupleAndNamedArg1.py
Normal file
@@ -0,0 +1,4 @@
|
||||
def f(a, b, c):
|
||||
pass
|
||||
|
||||
f(<arg_c>c=1, *(10, <arg_star>20))
|
||||
4
python/testData/paramInfo/TupleAndNamedArg2.py
Normal file
4
python/testData/paramInfo/TupleAndNamedArg2.py
Normal file
@@ -0,0 +1,4 @@
|
||||
def f(a, b, c):
|
||||
pass
|
||||
|
||||
f(*(10, <arg_star>20), <arg_c>c=1)
|
||||
@@ -290,6 +290,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self,a,b", new String[]{"b"}, new String[]{"self,"});
|
||||
}
|
||||
|
||||
public void testConstructorFactory() throws Exception {
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
assertEquals("Test data sanity", marks.size(), 1);
|
||||
|
||||
feignCtrlP(marks.get("<arg>").getTextOffset()).check("self,color", new String[]{"color"}, new String[]{"self,"});
|
||||
}
|
||||
|
||||
|
||||
public void testBoundMethodStatic() throws Exception {
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
assertEquals("Test data sanity", marks.size(), 2);
|
||||
@@ -323,6 +331,24 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("x,y=1,*args,**kwargs", new String[]{"**kwargs"});
|
||||
}
|
||||
|
||||
public void testTupleAndNamedArg1() throws Exception {
|
||||
// PY-1268
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
assertEquals("Test data sanity", marks.size(), 2);
|
||||
|
||||
feignCtrlP(marks.get("<arg_c>").getTextOffset()).check("a,b,c", new String[]{"c"});
|
||||
feignCtrlP(marks.get("<arg_star>").getTextOffset()).check("a,b,c", new String[]{"a,", "b,"});
|
||||
}
|
||||
|
||||
public void testTupleAndNamedArg2() throws Exception {
|
||||
// PY-1268
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
assertEquals("Test data sanity", marks.size(), 2);
|
||||
|
||||
feignCtrlP(marks.get("<arg_star>").getTextOffset()).check("a,b,c", new String[]{"a,", "b,"});
|
||||
feignCtrlP(marks.get("<arg_c>").getTextOffset()).check("a,b,c", new String[]{"c"});
|
||||
}
|
||||
|
||||
|
||||
// TODO: add method tests with decorators when a mock SDK is available
|
||||
|
||||
|
||||
Reference in New Issue
Block a user