mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Exclude resolve() from property detection, needed to build stubs right.
Special case of built-in call analysis added.
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveProcessor;
|
||||
import com.jetbrains.python.toolbox.Maybe;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -58,9 +61,13 @@ public abstract class PropertyBunch<MType> {
|
||||
if (source instanceof PyCallExpression) {
|
||||
final PyCallExpression call = (PyCallExpression)source;
|
||||
PyExpression callee = call.getCallee();
|
||||
if (callee instanceof PyReferenceExpression && "property".equals(callee.getName())) {
|
||||
PsiElement resolved = ((PyReferenceExpression)callee).getReference().resolve();
|
||||
if (resolved != null && PyBuiltinCache.getInstance(source).hasInBuiltins(resolved)) {
|
||||
if (callee instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression ref = (PyReferenceExpression)callee;
|
||||
if (ref.getQualifier() != null) return null;
|
||||
if ("property".equals(callee.getName()) && !resolvesLocally(ref)) {
|
||||
// we assume that a non-local name 'property' is a built-in name.
|
||||
// ref.resolve() is not used because we run in stub building phase where resolve() is frowned upon.
|
||||
// NOTE: this logic fails if (quite unusually) name 'property' is directly imported from builtins.
|
||||
return call;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +75,27 @@ public abstract class PropertyBunch<MType> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve in containing file only.
|
||||
* @param ref what to resolve
|
||||
* @return true iff ref obviously resolves to a local name (maybe partially, e.g. via import).
|
||||
*/
|
||||
protected static boolean resolvesLocally(@NotNull PyReferenceExpression ref) {
|
||||
final String name = ref.getName();
|
||||
if (name != null) {
|
||||
PsiElement outermost_context = ref;
|
||||
PsiElement seeker = ref;
|
||||
do {
|
||||
seeker = PsiTreeUtil.getParentOfType(seeker, PyFunction.class);
|
||||
if (seeker != null) outermost_context = seeker;
|
||||
} while (seeker != null);
|
||||
final ResolveProcessor processor = new ResolveProcessor(name);
|
||||
PyResolveUtil.treeCrawlUp(processor, true, outermost_context);
|
||||
return (processor.getResult() != null || processor.getDefiners().size() > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to form a bunch from data available at a possible property() call site.
|
||||
* @param source should be a PyCallExpression (if not, null is immediately returned).
|
||||
@@ -79,55 +107,58 @@ public abstract class PropertyBunch<MType> {
|
||||
if (call != null) {
|
||||
PyArgumentList arglist = call.getArgumentList();
|
||||
if (arglist != null) {
|
||||
PyArgumentList.AnalysisResult analysis = arglist.analyzeCall();
|
||||
PyCallExpression.PyMarkedCallee marked_callee = analysis.getMarkedCallee();
|
||||
if (marked_callee != null) {
|
||||
PyParameter[] params = marked_callee.getCallable().getParameterList().getParameters();
|
||||
List<Maybe<MType>> accessors = new ArrayList<Maybe<MType>>(3);
|
||||
final Maybe<MType> unknown = new Maybe<MType>();
|
||||
accessors.add(null); accessors.add(null); accessors.add(null); // 3 times
|
||||
final int offset = marked_callee.getImplicitOffset();
|
||||
for (Map.Entry<PyExpression, PyNamedParameter> entry: analysis.getPlainMappedParams().entrySet()) {
|
||||
PyNamedParameter param = entry.getValue();
|
||||
int n = ArrayUtil.indexOf(params, param) - offset;
|
||||
if (n >= 0) {
|
||||
if (n < 3) {
|
||||
// accessors
|
||||
accessors.set(n, unknown); // definitely filled
|
||||
PyExpression expr = entry.getKey();
|
||||
if (expr instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression arg_ref = (PyReferenceExpression)expr;
|
||||
if (arg_ref.getQualifier() == null) accessors.set(n, new Maybe<MType>(target.translate(arg_ref)));
|
||||
PyArgumentList.AnalysisResult analysis = PyCallExpressionHelper.analyzeBuiltinCall(call);
|
||||
if (analysis != null) {
|
||||
PyCallExpression.PyMarkedCallee marked_callee = analysis.getMarkedCallee();
|
||||
if (marked_callee != null) {
|
||||
PyParameter[] params = marked_callee.getCallable().getParameterList().getParameters();
|
||||
List<Maybe<MType>> accessors = new ArrayList<Maybe<MType>>(3);
|
||||
final Maybe<MType> unknown = new Maybe<MType>();
|
||||
accessors.add(null);
|
||||
accessors.add(null);
|
||||
accessors.add(null); // 3 times
|
||||
final int offset = marked_callee.getImplicitOffset();
|
||||
for (Map.Entry<PyExpression, PyNamedParameter> entry : analysis.getPlainMappedParams().entrySet()) {
|
||||
PyNamedParameter param = entry.getValue();
|
||||
int n = ArrayUtil.indexOf(params, param) - offset;
|
||||
if (n >= 0) {
|
||||
if (n < 3) {
|
||||
// accessors
|
||||
accessors.set(n, unknown); // definitely filled
|
||||
PyExpression expr = entry.getKey();
|
||||
if (expr instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression arg_ref = (PyReferenceExpression)expr;
|
||||
if (arg_ref.getQualifier() == null) accessors.set(n, new Maybe<MType>(target.translate(arg_ref)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (n == 3) {
|
||||
// doc
|
||||
PyExpression expr = entry.getKey();
|
||||
if (expr instanceof PyStringLiteralExpression) {
|
||||
target.myDoc = ((PyStringLiteralExpression)expr).getStringValue();
|
||||
else if (n == 3) {
|
||||
// doc
|
||||
PyExpression expr = entry.getKey();
|
||||
if (expr instanceof PyStringLiteralExpression) {
|
||||
target.myDoc = ((PyStringLiteralExpression)expr).getStringValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (PyNamedParameter param : analysis.getKwdMappedParams()) {
|
||||
// can't extract values, but values are present
|
||||
int n = ArrayUtil.indexOf(params, param) - offset;
|
||||
if (n >= 0 && n < 3) accessors.set(n, unknown);
|
||||
}
|
||||
for (PyParameter param : analysis.getTupleMappedParams()) {
|
||||
// can't extract values, but values are present
|
||||
int n = ArrayUtil.indexOf(params, param) - offset;
|
||||
if (n >= 0 && n < 3) accessors.set(n, unknown);
|
||||
}
|
||||
// something could have been not set; this means None was implicitly passed
|
||||
for (int i = 0; i < 3; i += 1) if (accessors.get(i) == null) accessors.set(i, new Maybe<MType>(null));
|
||||
target.myGetter = accessors.get(0);
|
||||
target.mySetter = accessors.get(1);
|
||||
target.myDeleter = accessors.get(2);
|
||||
return true;
|
||||
}
|
||||
for (PyNamedParameter param : analysis.getKwdMappedParams()) {
|
||||
// can't extract values, but values are present
|
||||
int n = ArrayUtil.indexOf(params, param) - offset;
|
||||
if (n >= 0 && n < 3) accessors.set(n, unknown);
|
||||
}
|
||||
for (PyParameter param : analysis.getTupleMappedParams()) {
|
||||
// can't extract values, but values are present
|
||||
int n = ArrayUtil.indexOf(params, param) - offset;
|
||||
if (n >= 0 && n < 3) accessors.set(n, unknown);
|
||||
}
|
||||
// something could have been not set; this means None was implicitly passed
|
||||
for (int i=0; i < 3; i+=1) if (accessors.get(i) == null) accessors.set(i, new Maybe<MType>(null));
|
||||
target.myGetter = accessors.get(0);
|
||||
target.mySetter = accessors.get(1);
|
||||
target.myDeleter = accessors.get(2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} }
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
|
||||
}
|
||||
|
||||
public AnalysisResult analyzeCall() {
|
||||
final AnalysisResultImpl ret = new AnalysisResultImpl();
|
||||
final PyCallExpressionHelper.AnalysisResultImpl ret = new PyCallExpressionHelper.AnalysisResultImpl(this);
|
||||
PyExpression[] arguments = getArguments();
|
||||
// declaration-based checks
|
||||
// proper arglist is: [positional,...][name=value,...][*tuple,][**dict]
|
||||
@@ -231,480 +231,12 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
|
||||
PyCallExpression call = getCallExpression();
|
||||
if (call != null) {
|
||||
PyCallExpression.PyMarkedCallee resolved_callee = call.resolveCallee();
|
||||
ret.my_marked_func = resolved_callee;
|
||||
if (resolved_callee != null) {
|
||||
analyzeCall(arguments, resolved_callee, ret);
|
||||
ret.mapArguments(arguments, resolved_callee);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void analyzeCall(PyExpression[] arguments, PyCallExpression.PyMarkedCallee resolved_callee, AnalysisResultImpl ret) {
|
||||
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 *tuple. might be just boolean, but this way debugging is easier
|
||||
PyNamedParameter tuple_slot = null; // the **kwd
|
||||
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
|
||||
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 {
|
||||
ret.markArgument(arg, ArgFlag.IS_DUP_KWD);
|
||||
//getHolder().createErrorAnnotation(arg, "duplicate **arg");
|
||||
unmatched_args.remove(arg); // error. ignore later
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tuple_arg == null) {
|
||||
tuple_arg = star_arg;
|
||||
}
|
||||
else {
|
||||
ret.markArgument(arg, ArgFlag.IS_DUP_TUPLE);
|
||||
//getHolder().createErrorAnnotation(arg, "duplicate *arg");
|
||||
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()); // the self param
|
||||
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
|
||||
ret.my_plain_mapped_params.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, ret);
|
||||
visitor.enterTuple(a_param.getAsTuple()); // will recurse 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;
|
||||
}
|
||||
ret.my_plain_mapped_params.put(arg, tuple_slot);
|
||||
unmatched_arg_iter.remove(); // consumed as nameless
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
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.my_plain_mapped_params later
|
||||
seen_kwd = true;
|
||||
}
|
||||
else {
|
||||
//getHolder().createErrorAnnotation(arg, "duplicate arg '" + argname + "'");
|
||||
ret.markArgument(arg, 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 {
|
||||
ret.markArgument(arg, ArgFlag.IS_UNMAPPED);
|
||||
//getHolder().createErrorAnnotation(arg, "cannot appear past an *arg");
|
||||
unmatched_arg_iter.remove(); // it has been flagged, forget
|
||||
}
|
||||
}
|
||||
else if (seen_kwd && (arg != kwd_arg)) {
|
||||
ret.markArgument(arg, ArgFlag.IS_POS_PAST_KWD);
|
||||
//getHolder().createErrorAnnotation(arg, "non-keyword arg after keyword arg");
|
||||
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) {
|
||||
ret.my_plain_mapped_params.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) {
|
||||
ret.my_kwd_mapped_params.add(kwd_slot);
|
||||
unmatched_args.remove(kwd_arg);
|
||||
}
|
||||
if (tuple_arg != null && tuple_slot != null) {
|
||||
ret.my_tuple_mapped_params.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 && ret.my_tuple_mapped_params.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 && ret.my_kwd_mapped_params.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.
|
||||
ret.markArgument(arg, ArgFlag.IS_UNMAPPED);
|
||||
}
|
||||
// any params still unfilled?
|
||||
for (final PyNamedParameter param : unfilled_params.values()) {
|
||||
// getHolder().createErrorAnnotation(close_paren, "parameter '" + param_name + "' unfilled");
|
||||
ret.my_unmapped_params.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()) ret.my_kwd_mapped_params.add(n_param);
|
||||
else ret.my_tuple_mapped_params.add(n_param);
|
||||
}
|
||||
else ret.my_plain_mapped_params.put(arg, n_param);
|
||||
}
|
||||
}
|
||||
}
|
||||
// copy starred args
|
||||
ret.my_kwd_arg = kwd_arg;
|
||||
ret.my_tuple_arg = tuple_arg;
|
||||
// add unmatched nested arguments
|
||||
for (PyExpression subarg : unmatched_subargs) {
|
||||
ret.my_arg_flags.put(subarg, EnumSet.of(ArgFlag.IS_UNMAPPED));
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyParamVisitor extends PyElementVisitor {
|
||||
private final Iterator<PyExpression> myArgIterator;
|
||||
private final AnalysisResultImpl myResult;
|
||||
private final List<PyExpression> myUnmatchedSubargs;
|
||||
|
||||
private MyParamVisitor(Iterator<PyExpression> arg_iterator, AnalysisResultImpl ret) {
|
||||
myArgIterator = arg_iterator;
|
||||
myResult = ret;
|
||||
myUnmatchedSubargs = new ArrayList<PyExpression>(5); // arbitrary 'enough'
|
||||
}
|
||||
|
||||
private Collection<PyExpression> getUnmatchedSubargs() {
|
||||
return myUnmatchedSubargs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyParameter(PyParameter node) {
|
||||
PyNamedParameter named = node.getAsNamed();
|
||||
if (named != null) enterNamed(named);
|
||||
else enterTuple(node.getAsTuple());
|
||||
}
|
||||
|
||||
public void enterTuple(PyTupleParameter param) {
|
||||
PyExpression arg = null;
|
||||
if (myArgIterator.hasNext()) arg = myArgIterator.next();
|
||||
// try to unpack a tuple expr in argument, if there's any
|
||||
PyExpression[] elements = null;
|
||||
if (arg instanceof PyParenthesizedExpression) {
|
||||
PyExpression inner_expr = ((PyParenthesizedExpression)arg).getContainedExpression();
|
||||
if (inner_expr instanceof PyTupleExpression) elements = ((PyTupleExpression)inner_expr).getElements();
|
||||
}
|
||||
else if (arg instanceof PyListLiteralExpression) {
|
||||
elements = ((PyListLiteralExpression)arg).getElements();
|
||||
}
|
||||
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);
|
||||
for (PyParameter nested : nested_params) nested.accept(visitor);
|
||||
myUnmatchedSubargs.addAll(visitor.getUnmatchedSubargs());
|
||||
}
|
||||
else { // map all what's inside to this arg
|
||||
final List<PyNamedParameter> nested_mapped = new ArrayList<PyNamedParameter>(nested_params.length);
|
||||
ParamHelper.walkDownParamArray(
|
||||
nested_params,
|
||||
new ParamHelper.ParamVisitor() {
|
||||
@Override public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
|
||||
nested_mapped.add(param);
|
||||
}
|
||||
}
|
||||
);
|
||||
myResult.my_nested_mapped_params.put(arg, nested_mapped);
|
||||
}
|
||||
}
|
||||
|
||||
public void enterNamed(PyNamedParameter param) {
|
||||
if (myArgIterator.hasNext()) {
|
||||
PyExpression subarg = myArgIterator.next();
|
||||
myResult.my_plain_mapped_params.put(subarg, param);
|
||||
}
|
||||
else {
|
||||
myResult.my_unmapped_params.add(param);
|
||||
}
|
||||
// ...and *arg or **arg just won't parse inside a tuple, no need to handle it here
|
||||
}
|
||||
}
|
||||
|
||||
private class AnalysisResultImpl implements PyArgumentList.AnalysisResult {
|
||||
|
||||
private final Map<PyExpression, PyNamedParameter> my_plain_mapped_params; // one param per arg
|
||||
private final Map<PyExpression, List<PyNamedParameter>> my_nested_mapped_params; // one arg sweeps a nested tuple of params
|
||||
private PyStarArgument my_tuple_arg; // the *arg
|
||||
private PyStarArgument my_kwd_arg; // the **arg
|
||||
private final List<PyNamedParameter> my_tuple_mapped_params; // params mapped to *arg
|
||||
private final List<PyNamedParameter> my_kwd_mapped_params; // params mapped to **arg
|
||||
private final List<PyNamedParameter> my_unmapped_params;
|
||||
private final Map<PyExpression, EnumSet<PyArgumentList.ArgFlag>> my_arg_flags; // flags of every arg
|
||||
private PyCallExpression.PyMarkedCallee my_marked_func;
|
||||
|
||||
public AnalysisResultImpl() {
|
||||
// full of empty containers
|
||||
my_plain_mapped_params = new HashMap<PyExpression, PyNamedParameter>();
|
||||
my_nested_mapped_params = new HashMap<PyExpression, List<PyNamedParameter>>();
|
||||
my_tuple_mapped_params = new ArrayList<PyNamedParameter>();
|
||||
my_kwd_mapped_params = new ArrayList<PyNamedParameter>();
|
||||
my_unmapped_params = new ArrayList<PyNamedParameter>();
|
||||
my_arg_flags = new HashMap<PyExpression, EnumSet<ArgFlag>>();
|
||||
my_marked_func = null;
|
||||
}
|
||||
|
||||
public boolean isImplicitlyResolved() {
|
||||
return my_marked_func == null ? false : my_marked_func.isImplicitlyResolved();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A mapping argument->parameter for non-starred arguments (but includes starred parameters).
|
||||
*/
|
||||
public @NotNull Map<PyExpression, PyNamedParameter> getPlainMappedParams() {
|
||||
return my_plain_mapped_params;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<PyExpression, List<PyNamedParameter>> getNestedMappedParams() {
|
||||
return my_nested_mapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return First *arg, or null.
|
||||
*/
|
||||
public PyStarArgument getTupleArg(){
|
||||
return my_tuple_arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of parameters mapped to an *arg.
|
||||
*/
|
||||
public @NotNull List<PyNamedParameter> getTupleMappedParams(){
|
||||
return my_tuple_mapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return First **arg, or null.
|
||||
*/
|
||||
public PyStarArgument getKwdArg(){
|
||||
return my_kwd_arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of parameters mapped to an **arg.
|
||||
*/
|
||||
public @NotNull List<PyNamedParameter> getKwdMappedParams(){
|
||||
return my_kwd_mapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of parameters for which no arguments were found ('missing').
|
||||
*/
|
||||
public @NotNull
|
||||
List<PyNamedParameter> getUnmappedParams(){
|
||||
return my_unmapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return result of a resolveCallee() against the function call to which the paramater list belongs.
|
||||
*/
|
||||
@Nullable
|
||||
public PyCallExpression.PyMarkedCallee getMarkedCallee() {
|
||||
return my_marked_func;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lists all args with their flags.
|
||||
*/
|
||||
public Map<PyExpression, EnumSet<ArgFlag>> getArgumentFlags(){
|
||||
return my_arg_flags;
|
||||
}
|
||||
|
||||
public PyArgumentList getArgumentList() {
|
||||
return PyArgumentListImpl.this; // that is, 'outer'
|
||||
}
|
||||
|
||||
protected PyExpression markArgument(PyExpression arg, ArgFlag... flags) {
|
||||
EnumSet<ArgFlag> argflags = my_arg_flags.get(arg);
|
||||
if (argflags == null) {
|
||||
argflags = EnumSet.noneOf(ArgFlag.class);
|
||||
}
|
||||
argflags.addAll(Arrays.asList(flags));
|
||||
my_arg_flags.put(arg, argflags);
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Functions common to different implementors of PyCallExpression, with different base classes.
|
||||
@@ -253,4 +254,516 @@ public class PyCallExpressionHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps arguments of a call assuming it's a call to a builtin. No resolution or assignment chasing is done inside;
|
||||
* caller should have done that.
|
||||
* Class to constructor mapping is made inside, though.
|
||||
* @param call an unqualified call expression; name is directly searched in builtins.
|
||||
* @return analysis with argument mapping.
|
||||
*/
|
||||
@Nullable
|
||||
public static PyArgumentList.AnalysisResult analyzeBuiltinCall(@NotNull PyCallExpression call) {
|
||||
final AnalysisResultImpl ret = new AnalysisResultImpl(call.getArgumentList());
|
||||
PyExpression[] arguments = call.getArguments();
|
||||
final String name = call.getCallee().getName();
|
||||
if (name != null) {
|
||||
PyFile builtins = PyBuiltinCache.getInstance(call).getBuiltinsFile();
|
||||
if (builtins != null) {
|
||||
int arg_offset = 0;
|
||||
EnumSet<PyFunction.Flag> flags = EnumSet.noneOf(PyFunction.Flag.class);
|
||||
PyFunction func = builtins.findTopLevelFunction(name);
|
||||
if (func == null) {
|
||||
PyClass cls = builtins.findTopLevelClass(name);
|
||||
if (cls != null) {
|
||||
func = cls.findInitOrNew(true);
|
||||
arg_offset = 1;
|
||||
}
|
||||
}
|
||||
if (func != null) {
|
||||
if (PyNames.NEW.equals(func.getName())) flags.add(PyFunction.Flag.CLASSMETHOD);
|
||||
ret.mapArguments(arguments, new PyCallExpression.PyMarkedCallee(func, flags, arg_offset, false));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static class MyParamVisitor extends PyElementVisitor {
|
||||
private final Iterator<PyExpression> myArgIterator;
|
||||
private final AnalysisResultImpl myResult;
|
||||
private final List<PyExpression> myUnmatchedSubargs;
|
||||
|
||||
private MyParamVisitor(Iterator<PyExpression> arg_iterator, AnalysisResultImpl ret) {
|
||||
myArgIterator = arg_iterator;
|
||||
myResult = ret;
|
||||
myUnmatchedSubargs = new ArrayList<PyExpression>(5); // arbitrary 'enough'
|
||||
}
|
||||
|
||||
private Collection<PyExpression> getUnmatchedSubargs() {
|
||||
return myUnmatchedSubargs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyParameter(PyParameter node) {
|
||||
PyNamedParameter named = node.getAsNamed();
|
||||
if (named != null) enterNamed(named);
|
||||
else enterTuple(node.getAsTuple());
|
||||
}
|
||||
|
||||
public void enterTuple(PyTupleParameter param) {
|
||||
PyExpression arg = null;
|
||||
if (myArgIterator.hasNext()) arg = myArgIterator.next();
|
||||
// try to unpack a tuple expr in argument, if there's any
|
||||
PyExpression[] elements = null;
|
||||
if (arg instanceof PyParenthesizedExpression) {
|
||||
PyExpression inner_expr = ((PyParenthesizedExpression)arg).getContainedExpression();
|
||||
if (inner_expr instanceof PyTupleExpression) elements = ((PyTupleExpression)inner_expr).getElements();
|
||||
}
|
||||
else if (arg instanceof PyListLiteralExpression) {
|
||||
elements = ((PyListLiteralExpression)arg).getElements();
|
||||
}
|
||||
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);
|
||||
for (PyParameter nested : nested_params) nested.accept(visitor);
|
||||
myUnmatchedSubargs.addAll(visitor.getUnmatchedSubargs());
|
||||
}
|
||||
else { // map all what's inside to this arg
|
||||
final List<PyNamedParameter> nested_mapped = new ArrayList<PyNamedParameter>(nested_params.length);
|
||||
ParamHelper.walkDownParamArray(
|
||||
nested_params,
|
||||
new ParamHelper.ParamVisitor() {
|
||||
@Override public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
|
||||
nested_mapped.add(param);
|
||||
}
|
||||
}
|
||||
);
|
||||
myResult.my_nested_mapped_params.put(arg, nested_mapped);
|
||||
}
|
||||
}
|
||||
|
||||
public void enterNamed(PyNamedParameter param) {
|
||||
if (myArgIterator.hasNext()) {
|
||||
PyExpression subarg = myArgIterator.next();
|
||||
myResult.my_plain_mapped_params.put(subarg, param);
|
||||
}
|
||||
else {
|
||||
myResult.my_unmapped_params.add(param);
|
||||
}
|
||||
// ...and *arg or **arg just won't parse inside a tuple, no need to handle it here
|
||||
}
|
||||
}
|
||||
|
||||
static class AnalysisResultImpl implements PyArgumentList.AnalysisResult {
|
||||
|
||||
private final Map<PyExpression, PyNamedParameter> my_plain_mapped_params; // one param per arg
|
||||
private final Map<PyExpression, List<PyNamedParameter>> my_nested_mapped_params; // one arg sweeps a nested tuple of params
|
||||
private PyStarArgument my_tuple_arg; // the *arg
|
||||
private PyStarArgument my_kwd_arg; // the **arg
|
||||
private final List<PyNamedParameter> my_tuple_mapped_params; // params mapped to *arg
|
||||
private final List<PyNamedParameter> my_kwd_mapped_params; // params mapped to **arg
|
||||
private final List<PyNamedParameter> my_unmapped_params;
|
||||
private final Map<PyExpression, EnumSet<PyArgumentList.ArgFlag>> my_arg_flags; // flags of every arg
|
||||
private PyCallExpression.PyMarkedCallee my_marked_callee;
|
||||
private PyArgumentList my_argument_list;
|
||||
|
||||
public AnalysisResultImpl(PyArgumentList arglist) {
|
||||
// full of empty containers
|
||||
my_plain_mapped_params = new HashMap<PyExpression, PyNamedParameter>();
|
||||
my_nested_mapped_params = new HashMap<PyExpression, List<PyNamedParameter>>();
|
||||
my_tuple_mapped_params = new ArrayList<PyNamedParameter>();
|
||||
my_kwd_mapped_params = new ArrayList<PyNamedParameter>();
|
||||
my_unmapped_params = new ArrayList<PyNamedParameter>();
|
||||
my_arg_flags = new HashMap<PyExpression, EnumSet<PyArgumentList.ArgFlag>>();
|
||||
my_marked_callee = null;
|
||||
my_argument_list = arglist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps arguments of a call to parameters of a callee.
|
||||
* must contain already resolved callee with flags set appropriately
|
||||
* @param arguments what to map, get if from call site
|
||||
* @param resolved_callee
|
||||
*/
|
||||
void mapArguments(PyExpression[] arguments, PyCallExpression.PyMarkedCallee resolved_callee) {
|
||||
my_marked_callee = 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 *tuple. might be just boolean, but this way debugging is easier
|
||||
PyNamedParameter tuple_slot = null; // the **kwd
|
||||
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
|
||||
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);
|
||||
//getHolder().createErrorAnnotation(arg, "duplicate **arg");
|
||||
unmatched_args.remove(arg); // error. ignore later
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tuple_arg == null) {
|
||||
tuple_arg = star_arg;
|
||||
}
|
||||
else {
|
||||
markArgument(arg, PyArgumentList.ArgFlag.IS_DUP_TUPLE);
|
||||
//getHolder().createErrorAnnotation(arg, "duplicate *arg");
|
||||
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()); // the self param
|
||||
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
|
||||
my_plain_mapped_params.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 recurse 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;
|
||||
}
|
||||
my_plain_mapped_params.put(arg, tuple_slot);
|
||||
unmatched_arg_iter.remove(); // consumed as nameless
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
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.my_plain_mapped_params later
|
||||
seen_kwd = true;
|
||||
}
|
||||
else {
|
||||
//getHolder().createErrorAnnotation(arg, "duplicate arg '" + argname + "'");
|
||||
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);
|
||||
//getHolder().createErrorAnnotation(arg, "cannot appear past an *arg");
|
||||
unmatched_arg_iter.remove(); // it has been flagged, forget
|
||||
}
|
||||
}
|
||||
else if (seen_kwd && (arg != kwd_arg)) {
|
||||
markArgument(arg, PyArgumentList.ArgFlag.IS_POS_PAST_KWD);
|
||||
//getHolder().createErrorAnnotation(arg, "non-keyword arg after keyword arg");
|
||||
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) {
|
||||
my_plain_mapped_params.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) {
|
||||
my_kwd_mapped_params.add(kwd_slot);
|
||||
unmatched_args.remove(kwd_arg);
|
||||
}
|
||||
if (tuple_arg != null && tuple_slot != null) {
|
||||
my_tuple_mapped_params.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 && my_tuple_mapped_params.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 && my_kwd_mapped_params.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");
|
||||
my_unmapped_params.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()) my_kwd_mapped_params.add(n_param);
|
||||
else my_tuple_mapped_params.add(n_param);
|
||||
}
|
||||
else my_plain_mapped_params.put(arg, n_param);
|
||||
}
|
||||
}
|
||||
}
|
||||
// copy starred args
|
||||
my_kwd_arg = kwd_arg;
|
||||
my_tuple_arg = tuple_arg;
|
||||
// add unmatched nested arguments
|
||||
for (PyExpression subarg : unmatched_subargs) {
|
||||
my_arg_flags.put(subarg, EnumSet.of(PyArgumentList.ArgFlag.IS_UNMAPPED));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isImplicitlyResolved() {
|
||||
return my_marked_callee == null ? false : my_marked_callee.isImplicitlyResolved();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A mapping argument->parameter for non-starred arguments (but includes starred parameters).
|
||||
*/
|
||||
public @NotNull
|
||||
Map<PyExpression, PyNamedParameter> getPlainMappedParams() {
|
||||
return my_plain_mapped_params;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<PyExpression, List<PyNamedParameter>> getNestedMappedParams() {
|
||||
return my_nested_mapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return First *arg, or null.
|
||||
*/
|
||||
public PyStarArgument getTupleArg(){
|
||||
return my_tuple_arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of parameters mapped to an *arg.
|
||||
*/
|
||||
public @NotNull List<PyNamedParameter> getTupleMappedParams(){
|
||||
return my_tuple_mapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return First **arg, or null.
|
||||
*/
|
||||
public PyStarArgument getKwdArg(){
|
||||
return my_kwd_arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of parameters mapped to an **arg.
|
||||
*/
|
||||
public @NotNull List<PyNamedParameter> getKwdMappedParams(){
|
||||
return my_kwd_mapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of parameters for which no arguments were found ('missing').
|
||||
*/
|
||||
public @NotNull
|
||||
List<PyNamedParameter> getUnmappedParams(){
|
||||
return my_unmapped_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return result of a resolveCallee() against the function call to which the paramater list belongs.
|
||||
*/
|
||||
@Nullable
|
||||
public PyCallExpression.PyMarkedCallee getMarkedCallee() {
|
||||
return my_marked_callee;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lists all args with their flags.
|
||||
*/
|
||||
public Map<PyExpression, EnumSet<PyArgumentList.ArgFlag>> getArgumentFlags(){
|
||||
return my_arg_flags;
|
||||
}
|
||||
|
||||
public PyArgumentList getArgumentList() {
|
||||
return my_argument_list; // that is, 'outer'
|
||||
}
|
||||
|
||||
protected PyExpression markArgument(PyExpression arg, PyArgumentList.ArgFlag... flags) {
|
||||
EnumSet<PyArgumentList.ArgFlag> argflags = my_arg_flags.get(arg);
|
||||
if (argflags == null) {
|
||||
argflags = EnumSet.noneOf(PyArgumentList.ArgFlag.class);
|
||||
}
|
||||
argflags.addAll(Arrays.asList(flags));
|
||||
my_arg_flags.put(arg, argflags);
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ public class PyResolveUtil {
|
||||
|
||||
/**
|
||||
* Returns closest previous node of given class, as input file would have it.
|
||||
* @param elt node from which to look for a previous atatement.
|
||||
* @param classes which class of the previous nodes to find.
|
||||
* @param elt node from which to look for a previous statement.
|
||||
* @param condition determines where a node is considered found.
|
||||
* @return previous statement, or null.
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user