Exclude resolve() from property detection, needed to build stubs right.

Special case of built-in call analysis added.
This commit is contained in:
Dmitry Cheryasov
2010-06-19 07:26:32 +03:00
parent 2b973a07ad
commit 750cf54913
4 changed files with 595 additions and 519 deletions
@@ -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