PY-1268, PY-2005, PY-312: new by-instance call detection logic.

Raw, needs cleanup, but passes all tests.
This commit is contained in:
Dmitry Cheryasov
2010-10-23 05:18:07 +03:00
parent 9601aed82b
commit 9d93dec82e
4 changed files with 293 additions and 18 deletions
@@ -44,7 +44,8 @@ public interface PyArgumentList extends PyElement {
/** unexpected */ IS_UNMAPPED,
/** duplicate **arg */ IS_DUP_KWD,
/** duplicate *arg */ IS_DUP_TUPLE,
/** positional past keyword */ IS_POS_PAST_KWD
/** positional past keyword */ IS_POS_PAST_KWD,
/** *param is too long */ IS_TOO_LONG,
}
@@ -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.mapArguments(arguments, resolved_callee, level);
ret.mapArguments2(arguments, resolved_callee, level);
}
}
return ret;
@@ -2,11 +2,13 @@ package com.jetbrains.python.psi.impl;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyTupleType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
@@ -138,7 +140,8 @@ public class PyCallExpressionHelper {
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 = isByInstance(callReference, context);
boolean is_by_instance = isConstructorCall || /*isByInstance(callReference, context);*/
divinate(us.getCallee(), (Callable)resolved, lastQualifier, context);
if (lastQualifier != null) {
PyType qualifier_type = context.getType(lastQualifier);
is_by_instance |=
@@ -202,8 +205,8 @@ public class PyCallExpressionHelper {
implicit_offset += 1;
} // Both Foo.method() and foo.method() have implicit the first arg
}
if (!isByInstance && PyNames.NEW.equals(method.getName())) implicit_offset += 1; // constructor call
// decorators?
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()
@@ -212,6 +215,8 @@ public class PyCallExpressionHelper {
implicit_offset += 1;
}
}
*/
// decorators?
// look for closest decorator
PyDecoratorList decolist = method.getDecoratorList();
if (decolist != null) {
@@ -253,6 +258,23 @@ public class PyCallExpressionHelper {
return false;
}
private static boolean divinate(PyExpression callee, Callable resolved, PyExpression lastQualifier, TypeEvalContext context) {
// true = call by instance
PyFunction method = resolved.asMethod();
if (method != null) {
if (lastQualifier == null) return true; // unqualified + method = implicit constructor call
PyType qtype = context.getType(lastQualifier);
if (qtype != null) {
if (qtype instanceof PyClassType) {
return ! ((PyClassType)qtype).isDefinition();
}
else return true; // TODO: handle UnionType
}
else return true; // NOTE. best guess: unknown qualifier is more probably an instance.
}
return false;
}
static boolean isCalleeText(PyCallExpression pyCallExpression, String[] nameCandidates) {
final PyExpression callee = pyCallExpression.getCallee();
if (!(callee instanceof PyReferenceExpression)) {
@@ -387,7 +409,7 @@ public class PyCallExpressionHelper {
else param_slots.put(a_param.getName(), null);
}
}
// look for star args
// look for star args, mark duplicate star args
for (PyExpression arg : arguments) {
if (arg instanceof PyStarArgument) {
final PyStarArgument star_arg = (PyStarArgument)arg;
@@ -395,7 +417,6 @@ public class PyCallExpressionHelper {
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
}
}
@@ -405,7 +426,6 @@ public class PyCallExpressionHelper {
}
else {
markArgument(arg, PyArgumentList.ArgFlag.IS_DUP_TUPLE);
//getHolder().createErrorAnnotation(arg, "duplicate *arg");
unmatched_args.remove(arg); // error. ignore later
}
}
@@ -414,7 +434,7 @@ public class PyCallExpressionHelper {
// 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_slots.remove(params[i].getName());
param_index += 1;
}
boolean seen_tuple_arg = false;
@@ -455,7 +475,7 @@ public class PyCallExpressionHelper {
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
visitor.enterTuple(a_param.getAsTuple()); // will recur as needed
unmatched_subargs.addAll(visitor.getUnmatchedSubargs()); // what it's seen
}
}
@@ -502,23 +522,18 @@ public class PyCallExpressionHelper {
// we'll put() it to ret.myPlainMappedParams later
seen_kwd = true;
}
else {
//getHolder().createErrorAnnotation(arg, "duplicate arg '" + argname + "'");
markArgument(arg, PyArgumentList.ArgFlag.IS_DUP);
}
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);
//getHolder().createErrorAnnotation(arg, "cannot appear past an *arg");
unmatched_arg_iter.remove(); // it has been flagged, forget
}
}
else if (seen_kwd && (arg != kwd_arg)) {
else if (seen_kwd && (arg != kwd_arg) && (arg != tuple_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);
@@ -673,6 +688,265 @@ public class PyCallExpressionHelper {
}
}
void mapArguments2(PyExpression[] arguments, PyCallExpression.PyMarkedCallee resolved_callee, LanguageLevel language_level) {
TypeEvalContext type_context = TypeEvalContext.fast(); // TODO: get it from parameters
myMarkedCallee = resolved_callee;
List<PyExpression> unmatched_args = new LinkedList<PyExpression>();
Collections.addAll(unmatched_args, arguments);
final List<PyExpression> unmatched_subargs = new LinkedList<PyExpression>(); // unmatched nested arguments will go here
// detect starred args
for (PyExpression arg : arguments) {
if (arg instanceof PyStarArgument) {
PyStarArgument star_arg = (PyStarArgument)arg;
if (star_arg.isKeyword()) {
if (myKwdArg == null) myKwdArg = star_arg;
else {
markArgument(arg, PyArgumentList.ArgFlag.IS_DUP_KWD);
unmatched_args.remove(arg);
}
}
else {
if (myTupleArg == null) myTupleArg = star_arg;
else {
markArgument(arg, PyArgumentList.ArgFlag.IS_DUP_TUPLE);
unmatched_args.remove(arg);
}
}
}
}
// prepare parameter slots
final PyParameter[] parameters = myMarkedCallee.getCallable().getParameterList().getParameters();
Map<PyNamedParameter, PyExpression> slots = new HashMap<PyNamedParameter, PyExpression>();
PyNamedParameter kwd_par = null; // **param
PyNamedParameter tuple_par = null; // *param
Set<PyExpression> mapped_args = new HashSet<PyExpression>();
final int implicit_offset = resolved_callee.getImplicitOffset();
int positional_index = 0; // up to this index parameters are positional
// check positional arguments, fill slots
int i = 0;
for (PyParameter par : parameters) {
if (tuple_par == null && kwd_par == null && positional_index < implicit_offset) {
positional_index += 1;
continue;
}
PyNamedParameter n_par = par.getAsNamed();
if (n_par != null) {
if (n_par.isPositionalContainer()) tuple_par = n_par;
else if (n_par.isKeywordContainer()) kwd_par = n_par;
else {
slots.put(n_par, null); // regular parameter that may serve as positional
positional_index += 1;
}
}
else {
PyTupleParameter t_par = par.getAsTuple();
if (t_par != null) positional_index += 1; // tuple can only be positional
// else lone star, skip
}
i += 1;
}
// rule out 'self' or other implicit params
for (i=0; i < implicit_offset && i < parameters.length; i+=1) {
slots.remove(parameters[i].getAsNamed());
positional_index += 1;
}
// now params to the left of positional_index are positional.
// map positional args to positional params.
// we assume that implicitly skipped parameters are never nested tuples. no idea when they could ever be.
int cnt = implicit_offset;
int positional_bound = arguments.length; // to the right of this pos args are verboten
ListIterator<PyExpression> unmatched_arg_iter = unmatched_args.listIterator();
while (unmatched_arg_iter.hasNext()) {
PyExpression arg = unmatched_arg_iter.next();
if (arg instanceof PyStarArgument || arg instanceof PyKeywordArgument) {
positional_bound = cnt;
break;
}
if (cnt < parameters.length && cnt < positional_index) {
final PyParameter par = parameters[cnt];
PyNamedParameter n_par = par.getAsNamed();
if (n_par != null) {
cnt += 1;
slots.put(n_par, arg);
mapped_args.add(arg);
}
else {
PyTupleParameter t_par = par.getAsTuple();
if (t_par != null) {
if (arg instanceof PyParenthesizedExpression) {
mapped_args.add(arg); // tuple itself is always mapped; its insides can fail
}
unmatched_arg_iter.previous();
MyParamVisitor visitor = new MyParamVisitor(unmatched_arg_iter, this);
visitor.enterTuple(t_par.getAsTuple()); // will recur as needed
unmatched_subargs.addAll(visitor.getUnmatchedSubargs()); // what didn't match inside
cnt += 1;
}
// else: goes to *param
}
}
else break;
}
// anything left after mapping of tuple params?
for (Map.Entry<PyExpression, List<PyNamedParameter>> pair : myNestedMappedParams.entrySet()) {
PyExpression arg = pair.getKey();
List<PyNamedParameter> params = pair.getValue();
mapped_args.add(arg);
for (PyNamedParameter n_par : params) slots.remove(n_par);
}
for (PyExpression arg : unmatched_subargs) {
markArgument(arg, PyArgumentList.ArgFlag.IS_UNMAPPED);
}
// mark past-bound positional args
i = positional_bound;
while (i<arguments.length) {
PyExpression arg = arguments[i];
if (!(arg instanceof PyStarArgument) && !(arg instanceof PyKeywordArgument)) {
markArgument(arg, PyArgumentList.ArgFlag.IS_POS_PAST_KWD);
}
i += 1;
}
// map named args to named params if possible
Map<String, PyNamedParameter> parameter_by_name = new HashMap<String, PyNamedParameter>();
for (PyParameter par : parameters) {
PyNamedParameter n_par = par.getAsNamed();
if (n_par != null) parameter_by_name.put(n_par.getName(), n_par);
}
for (PyExpression arg : arguments) {
if (arg instanceof PyKeywordArgument) { // to explicitly named param?
String arg_name = ((PyKeywordArgument)arg).getKeyword();
PyNamedParameter respective_par = parameter_by_name.get(arg_name);
if (respective_par != null && !respective_par.isKeywordContainer() && !respective_par.isPositionalContainer()) {
if (slots.get(respective_par) != null) markArgument(arg, PyArgumentList.ArgFlag.IS_DUP);
else slots.put(respective_par, arg);
}
else { // to **param?
if (kwd_par != null) {
myPlainMappedParams.put(arg, kwd_par);
mapped_args.add(arg);
}
}
}
}
// map *arg to positional params if possible
if (cnt < parameters.length && cnt < positional_index && myTupleArg != null) {
// check length of myTupleArg
PyType tuple_arg_type = null;
if (type_context != null) {
tuple_arg_type = type_context.getType(PsiTreeUtil.getChildOfType(myTupleArg, PyExpression.class));
}
int tuple_length = -1;
boolean tuple_length_known = false;
if (tuple_arg_type instanceof PyTupleType) {
tuple_length = ((PyTupleType)tuple_arg_type).getElementCount();
tuple_length_known = true;
}
i = 1;
while (cnt < parameters.length && cnt < positional_index) {
PyParameter par = parameters[cnt];
if (par instanceof PySingleStarParameter) break;
PyNamedParameter n_par = par.getAsNamed();
if (slots.containsKey(n_par)) {
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;
}
else if (n_par != null) { // normally always true
myTupleMappedParams.add(n_par);
mapped_args.add(myTupleArg);
slots.remove(n_par);
}
}
cnt += 1;
i += 1;
}
if (tuple_length_known && i <= tuple_length) {
markArgument(myTupleArg, PyArgumentList.ArgFlag.IS_TOO_LONG);
}
}
// map *param to the leftmost chunk of unmapped positional args
// NOTE: will fail on nested-tuple params!
if (tuple_par != null) {
i = 0;
while (i < arguments.length && mapped_args.contains(arguments[i]) && isPositionalArg(arguments[i])) {
i += 1; // skip first mapped args
}
if (i < arguments.length && isPositionalArg(arguments[i])) {
while (i < arguments.length && !mapped_args.contains(arguments[i]) && isPositionalArg(arguments[i])) {
myPlainMappedParams.put(arguments[i], tuple_par);
mapped_args.add(arguments[i]);
i += 1;
}
}
}
// map unmapped *arg to *param
if (myTupleArg != null && !mapped_args.contains(myTupleArg) && tuple_par != null) {
myTupleMappedParams.add(tuple_par);
mapped_args.add(myTupleArg);
}
// map unmapped named params to **kwarg
if (myKwdArg != null) {
for (PyParameter par : parameters) {
PyNamedParameter n_par = par.getAsNamed();
if (n_par != null && !n_par.isKeywordContainer() && !n_par.isPositionalContainer() && slots.get(n_par) == null) {
slots.put(n_par, myKwdArg);
}
}
}
// map unmapped **kwarg to **param
if (myKwdArg != null && kwd_par != null && !mapped_args.contains(myKwdArg)) {
myKwdMappedParams.add(kwd_par);
mapped_args.add(myKwdArg);
}
// fill in ret, mark unmapped named params
for (Map.Entry<PyNamedParameter, PyExpression> pair : slots.entrySet()) {
PyNamedParameter n_par = pair.getKey();
PyExpression arg = pair.getValue();
if (arg == null) {
if (!n_par.hasDefaultValue()) myUnmappedParams.add(n_par);
}
else {
if (arg == myTupleArg) {
myTupleMappedParams.add(n_par);
}
else if (arg == myKwdArg) {
myKwdMappedParams.add(n_par);
}
else {
myPlainMappedParams.put(arg, n_par);
}
}
}
// mark unmapped args
for (PyExpression arg : slots.values()) {
if (arg != null) mapped_args.add(arg);
}
for (PyExpression arg : arguments) {
if (!mapped_args.contains(arg)) {
final EnumSet<PyArgumentList.ArgFlag> flags = myArgFlags.get(arg);
if (flags == null || flags.isEmpty()) {
markArgument(arg, PyArgumentList.ArgFlag.IS_UNMAPPED);
}
}
}
}
private static boolean isPositionalArg(PyExpression arg) {
return !(arg instanceof PyKeywordArgument) && !(arg instanceof PyStarArgument);
}
public boolean isImplicitlyResolved() {
return myMarkedCallee == null ? false : myMarkedCallee.isImplicitlyResolved();
}
@@ -105,7 +105,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
public QualifiedResolveResult followAssignmentsChain(TypeEvalContext context) {
PyReferenceExpression seeker = this;
QualifiedResolveResult ret = null;
PyExpression last_qualifier = null;
PyExpression last_qualifier = seeker.getQualifier();
Set<PyExpression> visited = new HashSet<PyExpression>();
visited.add(this);
SEARCH: