Closes PY-32, PY-64 (a nice number coincidence).

This commit is contained in:
Dmitry Cheryasov
2008-09-06 08:16:26 +04:00
parent a47bf476ab
commit b07a4c82f8
4 changed files with 487 additions and 59 deletions
@@ -3,13 +3,19 @@ package com.jetbrains.python;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.lang.parameterInfo.*;
import com.jetbrains.python.psi.*;
import static com.jetbrains.python.psi.PyCallExpression.*;
import static com.jetbrains.python.psi.PyCallExpression.Flag;
import static com.jetbrains.python.psi.PyCallExpression.PyMarkedFunction;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Array;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
/**
* @author yole
*/
public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentList, PyCallExpression.PyMarkedFunction> {
public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentList, PyArgumentList.AnalysisResult> {
public boolean couldShowInLookup() {
return true;
@@ -18,31 +24,17 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
return new Object[0]; //To change body of implemented methods use File | Settings | File Templates.
}
public Object[] getParametersForDocumentation(final PyCallExpression.PyMarkedFunction p, final ParameterInfoContext context) {
public Object[] getParametersForDocumentation(final PyArgumentList.AnalysisResult p, final ParameterInfoContext context) {
return new Object[0]; //To change body of implemented methods use File | Settings | File Templates.
}
public PyArgumentList findElementForParameterInfo(final CreateParameterInfoContext context) {
PyArgumentList result = findArgumentList(context);
if (result != null) {
PyCallExpression callExpression = result.getCallExpression(); /*PsiTreeUtil.getParentOfType(result, PyCallExpression.class);*/
if (callExpression == null) return null;
/*
boolean is_inst = callExpression.isByInstance();
PyElement callee = callExpression.resolveCallee();
if (callee instanceof PyClass) { // constructor call
final PyClass cls = (PyClass)callee;
callee = cls.findMethodByName("__init__");
is_inst |= true;
}
if (!(callee instanceof PyFunction)) return null;
PyFunction function = (PyFunction) callee;
context.setItemsToShow(new Object[] { new PyCallExpression.PyMarkedFunction(function, is_inst) });
*/
context.setItemsToShow(new Object[] { callExpression.resolveCallee() });
PyArgumentList arglist = findArgumentList(context);
if (arglist != null) {
PyArgumentList.AnalysisResult result = arglist.analyzeCall();
context.setItemsToShow(new Object[] { result });
}
return result;
return arglist;
}
private static PyArgumentList findArgumentList(final ParameterInfoContext context) {
@@ -74,43 +66,78 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
return true;
}
public void updateUI(final PyMarkedFunction p, final ParameterInfoUIContext context) {
if (p == null) return;
final PyFunction py_function = p.getFunction();
public void updateUI(final PyArgumentList.AnalysisResult result, final ParameterInfoUIContext context) {
if (result == null) return;
PyMarkedFunction marked = result.getMarkedFunction();
if (marked == null) return;
final PyFunction py_function = marked.getFunction();
if (py_function == null) return; // resolution failed
final PyParameterList parameterList = py_function.getParameterList();
final PyParameter[] params = parameterList.getParameters();
int currentParameterIndex = context.getCurrentParameterIndex() >= 0 ? context.getCurrentParameterIndex():params.length;
final PyParameter[] params = py_function.getParameterList().getParameters();
final PyArgumentList arglist = result.getArgumentList();
int arg_index = context.getCurrentParameterIndex() >= 0 ? context.getCurrentParameterIndex():params.length;
int highlightStartOffset = -1;
int highlightEndOffset = -1;
StringBuilder signatureBuilder = new StringBuilder();
for(int i=0; i<params.length; i++) {
if (i == currentParameterIndex) {
highlightStartOffset = signatureBuilder.length();
}
if (params [i].isPositionalContainer()) {
signatureBuilder.append("*");
}
else if (i == 0 && p.getFlags().contains(Flag.IMPLICIT_FIRST_ARG)) {
currentParameterIndex += 1;
continue;
}
else if (params [i].isKeywordContainer()) {
signatureBuilder.append("**");
}
signatureBuilder.append(params [i].getName());
if (i == currentParameterIndex) {
highlightEndOffset = signatureBuilder.length();
}
if (i < params.length-1) {
signatureBuilder.append(", ");
}
// param texts
String[] param_texts = new String[params.length];
for (int i = 0; i < param_texts.length; i += 1) {
StringBuilder strb = new StringBuilder();
final PyParameter param = params[i];
if (param.isKeywordContainer()) strb.append("**");
else if (param.isPositionalContainer()) strb.append("*");
strb.append(param.getName());
PyExpression default_v = param.getDefaultValue();
if (default_v != null) strb.append("=").append(PyResolveUtil.getReadableRepr(default_v));
if (i < param_texts.length-1) strb.append(",");
param_texts[i] = strb.toString();
}
// formatting
Map<PyParameter, Integer> param_indexes = new HashMap<PyParameter, Integer>();
for (int i=0; i < params.length; i += 1) param_indexes.put(params[i], i);
EnumSet<ParameterInfoUIContextEx.Flag>[] flags = (EnumSet<ParameterInfoUIContextEx.Flag>[])Array.newInstance(EnumSet.class, params.length);
// ^^ gotta hate the covariance issues
for (int i =0; i < flags.length; i += 1) flags[i] = EnumSet.noneOf(ParameterInfoUIContextEx.Flag.class);
if (marked.getFlags().contains(Flag.IMPLICIT_FIRST_ARG)) {
//arg_index -= 1; // argument 0 is parameter 1, thus kipping para,eter 0 which is 'self'
flags[0].add(ParameterInfoUIContextEx.Flag.STRIKEOUT); // show but mark as absent
}
int cur_arg_index = 0;
for (PyExpression arg : arglist.getArguments()) {
if (cur_arg_index == arg_index) {
PyParameter param = result.getPlainMappedParams().get(arg);
if (param != null) {
final Integer param_index = param_indexes.get(param);
if (param_index < flags.length) {
flags[param_index].add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
else if (arg == result.getTupleArg()) {
// mark all params that map to *arg
for (PyParameter tpar : result.getTupleMappedParams()) {
final Integer param_index = param_indexes.get(tpar);
if (param_index != null && param_index.intValue() < flags.length) flags[param_index.intValue()].add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
else if (arg == result.getKwdArg()) {
// mark all params that map to **arg
for (PyParameter tpar : result.getKwdMappedParams()) {
final Integer param_index = param_indexes.get(tpar);
if (param_index != null && param_index < flags.length) flags[param_index].add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
}
// else: stay unhilited
cur_arg_index += 1;
}
if (context instanceof ParameterInfoUIContextEx) {
final ParameterInfoUIContextEx pic = (ParameterInfoUIContextEx)context;
pic.setupUIComponentPresentation(param_texts, flags, false, context.getDefaultParameterColor());
}
/*
context.setupUIComponentPresentation(signatureBuilder.toString(), highlightStartOffset, highlightEndOffset, false, false, false,
context.getDefaultParameterColor());
*/
}
}
@@ -19,12 +19,12 @@ package com.jetbrains.python.psi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* Represents an argument list of a function call.
* User: yole
* Date: 29.05.2005
* Time: 13:44:24
* To change this template use File | Settings | File Templates.
*/
public interface PyArgumentList extends PyElement {
@@ -39,4 +39,79 @@ public interface PyArgumentList extends PyElement {
@Nullable
PyCallExpression getCallExpression();
/**
* Tries to map the argument list to callee's idea of parameters.
* @return a result object with mappings and diagnostic flags.
*/
AnalysisResult analyzeCall();
/**
* Flags to mark analysis results for an argument.
* Theoretically can be used together, but currently only make sense as a single value per argument.
*/
enum ArgFlag {
/** duplicate plain */ IS_DUP,
/** unexpected */ IS_UNMAPPED,
/** duplicate **arg */ IS_DUP_KWD,
/** duplicate *arg */ IS_DUP_TUPLE,
/** positional past keyword */ IS_POS_PAST_KWD
}
/**
* Result of analysis of argument list application to the callee.
* Contains neatly arranged lists and mappinga between arguments and parameters,
* including error diagnostics.
*/
interface AnalysisResult {
/**
* @return A mapping parameter->argument for non-starred parameters (but includes starred argument).
*/
@NotNull Map<PyExpression, PyParameter> getPlainMappedParams();
/**
* @return First *arg, or null.
*/
@Nullable
PyStarArgument getTupleArg();
/**
* @return A list of parameters mapped to a *arg.
*/
@NotNull List<PyParameter> getTupleMappedParams();
/**
* @return First **arg, or null.
*/
@Nullable
PyStarArgument getKwdArg();
/**
* @return A list of parameters mapped to an **arg.
*/
@NotNull List<PyParameter> getKwdMappedParams();
/**
* @return A list of parameters for which no arguments were found ('missing').
*/
@NotNull
List<PyParameter> getUnmappedParams();
/**
* @return Lists all args with their flags.
* @see ArgFlag
*/
Map<PyExpression, EnumSet<ArgFlag>> getArgumentFlags();
/**
* @return result of a resolveCallee() against the function call to which the paramater list belongs.
*/
@Nullable
PyCallExpression.PyMarkedFunction getMarkedFunction();
PyArgumentList getArgumentList();
}
}
@@ -18,9 +18,9 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyTokenTypes;
@@ -28,7 +28,7 @@ import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.*;
public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList {
public PyArgumentListImpl(ASTNode astNode) {
@@ -246,4 +246,326 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
}
}
public AnalysisResult analyzeCall() {
AnalysisResultImpl ret = new AnalysisResultImpl();
PyExpression[] arguments = getArguments();
// declaration-based checks
// proper arglist is: [positional,...][name=value,...][*tuple,][**dict]
// following the spec: http://docs.python.org/ref/calls.html
PyCallExpression call = getCallExpression();
if (call != null) {
PyCallExpression.PyMarkedFunction resolved_callee = call.resolveCallee();
ret.my_marked_func = resolved_callee;
if (resolved_callee != null) {
PyFunction func = resolved_callee.getFunction();
boolean implicit_self = resolved_callee.getFlags().contains(PyCallExpression.Flag.IMPLICIT_FIRST_ARG);
PyParameter[] params = func.getParameterList().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>();
PyParameter kwd_slot = null; // the *tuple. might be just boolean, but this way debugging is easier
PyParameter tuple_slot = null; // the **kwd
PyStarArgument kwd_arg = null;
PyStarArgument tuple_arg = null;
// all slots are initially empty, *x and **x are not among slots
for (PyParameter a_param : params) {
if (a_param.isPositionalContainer()) tuple_slot = a_param;
else if (a_param.isKeywordContainer()) kwd_slot = a_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'
int param_index = 0;
if (implicit_self && (params.length > 0)) {
param_slots.remove(params[0].getName()); // the self param
param_index = 1;
}
boolean seen_tuple_arg = false;
boolean seen_kwd_arg = false;
ListIterator<PyExpression> unmatched_arg_iter = unmatched_args.listIterator();
// check positional args
while (unmatched_arg_iter.hasNext() && (param_index < params.length)) {
PyExpression arg = unmatched_arg_iter.next(); // current arg
PyParameter param = params[param_index]; // its matching param
if (
arg instanceof PyKeywordArgument || arg instanceof PyStarArgument ||
param.isKeywordContainer() || param.isPositionalContainer()
) {
seen_tuple_arg |= (arg == tuple_arg);
seen_kwd_arg |= (arg == kwd_arg);
unmatched_arg_iter.previous(); // step back
break;
}
param_slots.put(param.getName(), arg); // it cannot yet contain this name unless function definition is broken
ret.my_plain_mapped_params.put(arg, param);
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 param : params) {
final String param_name = param.getName();
if (
(param.getDefaultValue() == null) && // 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 param : params) {
final String param_name = param.getName();
if (
(param.getDefaultValue() == null) && // 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, PyParameter> unfilled_params = new HashMap<String, PyParameter>();
for (PyParameter param : params) {
final String param_name = param.getName();
if (
(param.getDefaultValue() == null) && // 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, 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);
}
// any args left?
for (PyExpression arg : unmatched_args) {
//getHolder().createErrorAnnotation(arg, "unexpected arg");
ret.markArgument(arg, ArgFlag.IS_UNMAPPED);
}
// any params still unfilled?
for (final PyParameter 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 param : params) {
PyExpression arg = param_slots.get(param.getName());
if (arg != null) {
if (arg instanceof PyStarArgument) {
PyStarArgument star_arg = (PyStarArgument)arg;
if (star_arg.isKeyword()) ret.my_kwd_mapped_params.add(param);
else ret.my_tuple_mapped_params.add(param);
}
else ret.my_plain_mapped_params.put(arg, param);
}
}
// copy starred args
ret.my_kwd_arg = kwd_arg;
ret.my_tuple_arg = tuple_arg;
}
}
return ret;
}
protected /*static*/ class AnalysisResultImpl implements AnalysisResult {
protected Map<PyExpression, PyParameter> my_plain_mapped_params;
protected PyStarArgument my_tuple_arg;
protected PyStarArgument my_kwd_arg;
protected List<PyParameter> my_tuple_mapped_params;
protected List<PyParameter> my_kwd_mapped_params;
protected List<PyParameter> my_unmapped_params;
protected Map<PyExpression, EnumSet<ArgFlag>> my_arg_flags;
protected PyCallExpression.PyMarkedFunction my_marked_func;
public AnalysisResultImpl() {
// full of empty containers
my_plain_mapped_params = new HashMap<PyExpression, PyParameter>();
my_tuple_mapped_params = new ArrayList<PyParameter>();
my_kwd_mapped_params = new ArrayList<PyParameter>();
my_unmapped_params = new ArrayList<PyParameter>();
my_arg_flags = new HashMap<PyExpression, EnumSet<ArgFlag>>();
my_marked_func = null;
}
/**
* @return A mapping argument->parameter for non-starred arguments (but includes starred parameters).
*/
public @NotNull Map<PyExpression, PyParameter> getPlainMappedParams() {
return my_plain_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<PyParameter> 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<PyParameter> getKwdMappedParams(){
return my_kwd_mapped_params;
}
/**
* @return A list of parameters for which no arguments were found ('missing').
*/
public @NotNull
List<PyParameter> getUnmappedParams(){
return my_unmapped_params;
}
/**
* @return result of a resolveCallee() against the function call to which the paramater list belongs.
*/
@Nullable
public PyCallExpression.PyMarkedFunction getMarkedFunction() {
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;
}
}
}
@@ -0,0 +1,4 @@
def f():
def q():
return t<ref>arget
target = 1