Adds a notion of a tuple parameter. Adapts paramater and argument inspections and Ctrl+P handler to use it. Adds tests for Ctrl+P handler.

Closes PY-200.
This commit is contained in:
Dmitry Cheryasov
2009-07-17 11:07:31 +04:00
parent 3fdb9d655c
commit 90ce4a132c
62 changed files with 1553 additions and 589 deletions
@@ -1,10 +1,9 @@
package com.jetbrains.python.psi.impl;
import com.intellij.psi.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import com.jetbrains.python.psi.types.PyType;
@@ -36,10 +35,10 @@ public class PyJavaTypeProvider implements PyTypeProvider {
return null;
}
public PyType getParameterType(final PyParameter param, final PyFunction func) {
public PyType getParameterType(final PyNamedParameter param, final PyFunction func) {
if (!(param.getParent() instanceof PyParameterList)) return null;
PyParameter[] params = ((PyParameterList) param.getParent()).getParameters();
final int index = ArrayUtil.indexOf(params, param);
List<PyNamedParameter> params = ParamHelper.collectNamedParameters((PyParameterList) param.getParent());
final int index = params.indexOf(param);
if (index < 0) return null;
final List<PyType> superMethodParameterTypes = new ArrayList<PyType>();
PySuperMethodsSearch.search(func).forEach(new Processor<PsiElement>() {
@@ -48,6 +48,7 @@ INSP.must.have.first.parameter=Method must have a first parameter, usually calle
INSP.probably.mistyped.self=Did not you mean 'self'?
INSP.usually.named.self=Usually first parameter of a method is named 'self'
INSP.usually.named.cls=Usually first parameter of a classmethod is named 'cls'
INSP.first.param.must.not.be.tuple=First parameter of a non-static method must not be a tuple
# PyRedeclarationInspection
INSP.NAME.redeclaration=Names redeclared without usage
@@ -95,6 +96,14 @@ ANN.continue.outside.loop='continue' outside loop
ANN.cant.continue.in.finally='continue' not supported inside 'finally' clause
ANN.default.except.must.be.last=default 'except:' must be last
ANN.$0.both.global.and.param=Name ''{0}'' used both as a parameter and as a global
ANN.$0.assigned.before.global.decl=Name ''{0}'' is assigned before global declaration
ANN.duplicate.param.name=duplicate parameter name
ANN.starred.param.after.kwparam=* parameter after ** paremeter
ANN.regular.param.after.starred=regular parameter after * or ** parameter
ANN.non.default.param.after.default=non-default parameter follows default parameter
### parsing
PARSE.expected.expression=expression expected
PARSE.expected.rbracket=']' expected
@@ -18,10 +18,14 @@ public interface PyElementTypes {
TokenSet PARAMETER_LIST_SET = TokenSet.create(PARAMETER_LIST);
PyStubElementType<PyParameterStub, PyParameter> FORMAL_PARAMETER = new PyFormalParameterElementType();
PyStubElementType<PyNamedParameterStub, PyNamedParameter> NAMED_PARAMETER = new PyNamedParameterElementType();
PyStubElementType<PyTupleParameterStub, PyTupleParameter> TUPLE_PARAMETER = new PyTupleParameterElementType();
TokenSet PARAMETERS = TokenSet.create(NAMED_PARAMETER, TUPLE_PARAMETER);
PyStubElementType<PyDecoratorStub, PyDecorator> DECORATOR_CALL = new PyDecoratorCallElementType();
TokenSet FORMAL_PARAMETER_SET = TokenSet.create(FORMAL_PARAMETER);
TokenSet FORMAL_PARAMETER_SET = TokenSet.create(NAMED_PARAMETER);
// Deprecated PyElementType DECORATED_FUNCTION_DECLARATION = new PyElementType("DECORATED_FUNCTION_DECLARATION", PyDecoratedFunctionImpl.class);
PyElementType ARGUMENT_LIST = new PyElementType("ARGUMENT_LIST", PyArgumentListImpl.class);
@@ -123,6 +127,6 @@ public interface PyElementTypes {
PyElementType ELSE_PART = new PyElementType("ELSE_PART", PyElsePartImpl.class);
TokenSet PARTS = TokenSet.create(IF_PART_IF, IF_PART_ELIF, FOR_PART, WHILE_PART, TRY_PART, FINALLY_PART, ELSE_PART);
TokenSet ELIFS = TokenSet.create(IF_PART_ELIF);
TokenSet ELIFS = TokenSet.create(IF_PART_ELIF);
}
@@ -2,14 +2,15 @@ package com.jetbrains.python;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.lang.parameterInfo.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import com.intellij.util.text.CharArrayUtil;
import com.jetbrains.python.psi.*;
import static com.jetbrains.python.psi.PyCallExpression.PyMarkedFunction;
import com.jetbrains.python.psi.impl.ParamHelper;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Array;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
/**
* @author yole
@@ -20,11 +21,11 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
return true;
}
public Object[] getParametersForLookup(final LookupElement item, final ParameterInfoContext context) {
return new Object[0]; //To change body of implemented methods use File | Settings | File Templates.
return new Object[0]; // we don't
}
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.
return new Object[0]; // we don't
}
public PyArgumentList findElementForParameterInfo(final CreateParameterInfoContext context) {
@@ -51,17 +52,37 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
return findArgumentList(context);
}
public void updateParameterInfo(@NotNull final PyArgumentList o, final UpdateParameterInfoContext context) {
if (context.getParameterOwner() != o) {
/**
<b>Note: instead of parameter index, we directly store parameter's offset for later use.</b><br/>
We cannot store an index since we cannot determine what is an argument until we actually map arguments to parameters.
This is because a tuple in arguments may be a whole argument or map to a tuple parameter.
*/
public void updateParameterInfo(@NotNull final PyArgumentList arglist, final UpdateParameterInfoContext context) {
if (context.getParameterOwner() != arglist) {
context.removeHint();
return;
}
final int currentParameterIndex = ParameterInfoUtils.getCurrentParameterIndex(o.getNode(), context.getOffset(), PyTokenTypes.COMMA);
context.setCurrentParameter(currentParameterIndex);
// align offset to nearest expression; context may point to a space, etc.
List<PyExpression> flat_args = PyUtil.flattenedParens(arglist.getArguments());
int alleged_cursor_offset = context.getOffset(); // this is already shifted backwards to skip spaces
PsiFile file = context.getFile();
CharSequence chars = file.getViewProvider().getContents();
int offset = -1;
for (PyExpression arg : flat_args) {
TextRange range = arg.getTextRange();
// widen the range to include all whitespace around the arg
int left = CharArrayUtil.shiftBackward(chars, range.getStartOffset()-1, " \t\r\n");
int right = CharArrayUtil.shiftForwardCarefully(chars, range.getEndOffset(), " \t\r\n");
if (left <= alleged_cursor_offset && right >= alleged_cursor_offset) {
offset = range.getStartOffset();
break;
}
}
context.setCurrentParameter(offset);
}
public String getParameterCloseChars() {
return ",)";
return ",()"; // lpar may mean a nested tuple param, so it's included
}
public boolean tracksParameterIndex() {
@@ -74,81 +95,119 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
assert marked != null : "findElementForParameterInfo() did it wrong!";
final PyFunction py_function = marked.getFunction();
if (py_function == null) return; // resolution failed
final PyParameter[] params = py_function.getParameterList().getParameters();
PyParameter[] raw_params = py_function.getParameterList().getParameters();
final List<PyNamedParameter> n_param_list = new ArrayList<PyNamedParameter>(raw_params.length);
final List<String> hint_texts = new ArrayList<String>(raw_params.length);
// param -> hint index. indexes are not contiguous, because some hints are parentheses.
final Map<PyNamedParameter, Integer> param_indexes = new HashMap<PyNamedParameter, Integer>();
// formatting of hints: hint index -> flags. this includes flags for parens.
final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hint_flags = new HashMap<Integer, EnumSet<ParameterInfoUIContextEx.Flag>>();
// build the textual picture and the list of named parameters
ParamHelper.walkDownParamArray(
py_function.getParameterList().getParameters(),
new ParamHelper.ParamWalker() {
public void enterTupleParameter(PyTupleParameter param, boolean first, boolean last) {
hint_flags.put(hint_texts.size(), EnumSet.noneOf(ParameterInfoUIContextEx.Flag.class));
hint_texts.add("(");
}
public void leaveTupleParameter(PyTupleParameter param, boolean first, boolean last) {
hint_flags.put(hint_texts.size(), EnumSet.noneOf(ParameterInfoUIContextEx.Flag.class));
if (last) hint_texts.add(")");
else hint_texts.add("),");
}
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
n_param_list.add(param);
StringBuilder strb = new StringBuilder();
strb.append(param.getRepr(true));
if (! last) strb.append(",");
int hint_index = hint_texts.size();
param_indexes.put(param, hint_index);
hint_flags.put(hint_index, EnumSet.noneOf(ParameterInfoUIContextEx.Flag.class));
hint_texts.add(strb.toString());
}
}
);
final PyArgumentList arglist = result.getArgumentList();
int arg_index = context.getCurrentParameterIndex() >= 0 ? context.getCurrentParameterIndex():params.length;
// 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(PyUtil.getReadableRepr(default_v, true));
if (i < param_texts.length-1) strb.append(",");
param_texts[i] = strb.toString();
final int current_param_offset = context.getCurrentParameterIndex(); // in Python mode, we get an offset here, not an index!
// gray out enough first parameters as implicit
for (int i=0; i < marked.getImplicitOffset(); i += 1) {
hint_flags.get(param_indexes.get(n_param_list.get(i))).add(ParameterInfoUIContextEx.Flag.DISABLE); // show but mark as absent
}
// 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);
// disable implicit params
for (int i=0; i < marked.getImplicitOffset(); i +=1) {
flags[i].add(ParameterInfoUIContextEx.Flag.DISABLE); // 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);
// highlight current param(s)
for (PyExpression arg : PyUtil.flattenedParens(arglist.getArguments())) {
if (arg.getTextRange().contains(current_param_offset)) {
PyNamedParameter 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);
if (param_index < hint_flags.size()) {
hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
else if (arg == result.getTupleArg()) {
// mark all params that map to *arg
for (PyParameter tpar : result.getTupleMappedParams()) {
for (PyNamedParameter 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);
if (param_index != null && param_index < hint_flags.size()) {
hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
}
else if (arg == result.getKwdArg()) {
// mark all params that map to **arg
for (PyParameter tpar : result.getKwdMappedParams()) {
// mark all n_params that map to **arg
for (PyNamedParameter 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);
if (param_index != null && param_index < hint_flags.size()) {
hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
}
else {
// maybe it's mapped to a nested tuple?
List<PyNamedParameter> nparams = result.getNestedMappedParams().get(arg);
if (nparams != null) {
for (PyNamedParameter tpar : nparams) {
final Integer param_index = param_indexes.get(tpar);
if (param_index != null && param_index < hint_flags.size()) {
hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
}
}
}
}
// else: stay unhilited
cur_arg_index += 1;
}
final String NO_PARAMS_MSG = "<No parameters>";
String[] hints = hint_texts.toArray(new String[hint_texts.size()]);
if (context instanceof ParameterInfoUIContextEx) {
final ParameterInfoUIContextEx pic = (ParameterInfoUIContextEx)context;
if (param_texts.length < 1) {
param_texts = new String[]{NO_PARAMS_MSG};
EnumSet<ParameterInfoUIContextEx.Flag>[] flags = new EnumSet[hint_flags.size()];
for (int i = 0; i < flags.length; i += 1) flags[i] = hint_flags.get(i);
if (hints.length < 1) {
hints = new String[]{NO_PARAMS_MSG};
flags = new EnumSet[]{EnumSet.of(ParameterInfoUIContextEx.Flag.DISABLE)};
}
pic.setupUIComponentPresentation(param_texts, flags, context.getDefaultParameterColor());
pic.setupUIComponentPresentation(hints, flags, context.getDefaultParameterColor());
}
else { // fallback, no hilite
StringBuffer signatureBuilder = new StringBuffer();
if (param_texts.length > 1) {
for (String s : param_texts) signatureBuilder.append(s);
StringBuilder signatureBuilder = new StringBuilder();
if (hints.length > 1) {
for (String s : hints) signatureBuilder.append(s);
}
else signatureBuilder.append(NO_PARAMS_MSG);
context.setupUIComponentPresentation(signatureBuilder.toString(), -1, 0, false, false, false,
context.getDefaultParameterColor());
context.setupUIComponentPresentation(
signatureBuilder.toString(), -1, 0, false, false, false, context.getDefaultParameterColor()
);
}
}
}
@@ -37,7 +37,7 @@ public class PythonFindUsagesProvider implements FindUsagesProvider {
@NotNull
public String getType(@NotNull PsiElement element) {
if (element instanceof PyParameter) return "parameter";
if (element instanceof PyNamedParameter) return "parameter";
if (element instanceof PyFunction) return "function";
if (element instanceof PyClass) return "class";
if (element instanceof PyReferenceExpression || element instanceof PyTargetExpression) return "variable";
@@ -13,6 +13,7 @@ import com.intellij.psi.PsiWhiteSpace;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import org.jetbrains.annotations.NotNull;
/**
@@ -90,11 +91,17 @@ public class AddMethodQuickFix implements LocalQuickFix {
private static void showTemplateBuilder(PyFunction method) {
method = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(method);
TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(method);
PyParameter[] parameters = method.getParameterList().getParameters();
for (int i = 1; i < parameters.length; i++) {
builder.replaceElement(parameters [i], parameters [i].getName());
}
final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(method);
ParamHelper.walkDownParamArray(
method.getParameterList().getParameters(),
new ParamHelper.ParamVisitor() {
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
builder.replaceElement(param, param.getName());
}
}
);
builder.replaceElement(method.getStatementList(), "pass");
builder.run();
@@ -8,7 +8,7 @@ import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.NonNls;
@@ -41,7 +41,7 @@ public class AddSelfQuickFix implements LocalQuickFix {
final PythonLanguage pythonLanguage = (PythonLanguage)language;
PyElementGenerator generator = pythonLanguage.getElementGenerator();
// TODO: generalize, move to generator
PyParameter new_param = generator.createFromText(project, PyParameter.class, "def f(self): pass", new int[]{0, 3, 1});
PyNamedParameter new_param = generator.createFromText(project, PyNamedParameter.class, "def f(self): pass", new int[]{0, 3, 1});
param_list.addParameter(new_param);
}
}
@@ -3,6 +3,7 @@ package com.jetbrains.python.actions;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -92,13 +93,7 @@ class ImportCandidateHolder {
parent = myImportElement.getParent();
}
if (myImportable instanceof PyFunction) {
sb.append("(");
// below: ", ".join([x.getRepr(False) for x in getParameters()])
PyParameter[] params = ((PyFunction)myImportable).getParameterList().getParameters();
String[] param_reprs = new String[params.length];
for (int i=0; i < params.length; i += 1) param_reprs[i] = params[i].getRepr(false);
PyUtil.joinSubarray(param_reprs, 0, params.length, ", ", sb);
sb.append(")");
ParamHelper.appendParameterList(((PyFunction)myImportable).getParameterList(), sb);
}
else if (myImportable instanceof PyClass) {
PyClass[] supers = ((PyClass)myImportable).getSuperClasses();
@@ -9,7 +9,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyNamedParameter;
import org.jetbrains.annotations.NotNull;
/**
@@ -22,10 +22,10 @@ public class RenameToSelfQuickFix implements LocalQuickFix {
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
final PsiElement elt = descriptor.getPsiElement();
if (elt != null && elt instanceof PyParameter && elt.isWritable()) {
if (elt != null && elt instanceof PyNamedParameter && elt.isWritable()) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
final PyParameter the_self = PythonLanguage.getInstance().getElementGenerator().createParameter(project, "self");
final PyNamedParameter the_self = PythonLanguage.getInstance().getElementGenerator().createParameter(project, "self");
try {
elt.replace(the_self);
}
@@ -68,13 +68,13 @@ public class PyArgumentListInspection extends LocalInspectionTool {
// X XX debug
PyCallExpression call_ex = node.getCallExpression();
System.out.println(PyResolveUtil.getReadableRepr(call_ex));
for (Map.Entry<PyExpression, PyParameter> entry : result.getPlainMappedParams().entrySet()) {
for (Map.Entry<PyExpression, PyNamedParameter> entry : result.getPlainMappedParams().entrySet()) {
System.out.println(
PyResolveUtil.getReadableRepr(entry.getValue()) +
" -> " + PyResolveUtil.getReadableRepr(entry.getKey())
PyCallExpression call_ex = node.getCallExpression();
System.out.println(PyResolveUtil.getReadableRepr(call_ex));
for (Map.Entry<PyExpression, PyParameter> entry : result.getPlainMappedParams().entrySet()) {
for (Map.Entry<PyExpression, PyNamedParameter> entry : result.getPlainMappedParams().entrySet()) {
System.out.println(
PyResolveUtil.getReadableRepr(entry.getValue()) +
" -> " + PyResolveUtil.getReadableRepr(entry.getKey())
@@ -116,16 +116,21 @@ public class PyArgumentListInspection extends LocalInspectionTool {
PyFunction decofunc = mkfunc.getFunction();
int first_param_offset = mkfunc.getImplicitOffset();
PyParameter[] params = decofunc.getParameterList().getParameters();
if (params.length < first_param_offset || params[first_param_offset-1].isKeywordContainer()) {
// no paramaters left to pass function implicitly, or wrong param type
PyNamedParameter alleged_first_param = params[first_param_offset-1].getAsNamed();
if (params.length < first_param_offset || alleged_first_param == null || alleged_first_param.isKeywordContainer()) {
// no parameters left to pass function implicitly, or wrong param type
registerProblem(deco, PyBundle.message("INSP.func.$0.lacks.first.arg", decofunc.getName()));
}
else {
// possible unfilled params
for (int i=first_param_offset; i < params.length; i += 1) {
PyParameter par = params[i];
if (! par.isKeywordContainer() && ! par.isPositionalContainer() && (par.getDefaultValue() == null)) {
registerProblem(deco, PyBundle.message("INSP.parameter.$0.unfilled", par.getName()));
PyNamedParameter par = params[i].getAsNamed();
// param tuples, non-starred or non-default won't do
if (par == null || (! par.isKeywordContainer() && ! par.isPositionalContainer() && (par.getDefaultValue() == null))) {
String par_name;
if (par != null) par_name = par.getName();
else par_name = "(...)"; // can't be bothered to find the first non-tuple inside it
registerProblem(deco, PyBundle.message("INSP.parameter.$0.unfilled", par_name));
}
}
}
@@ -166,7 +171,7 @@ public class PyArgumentListInspection extends LocalInspectionTool {
if (our_node != null) {
ASTNode close_paren = our_node.findChildByType(PyTokenTypes.RPAR);
if (close_paren != null) {
for (PyParameter param : result.getUnmappedParams()) {
for (PyNamedParameter param : result.getUnmappedParams()) {
holder.registerProblem(close_paren.getPsi(), PyBundle.message("INSP.parameter.$0.unfilled", param.getName()));
}
}
@@ -72,7 +72,8 @@ public class PyMethodFirstArgAssignmentInspection extends LocalInspectionTool {
// what is our first param?
PyParameter[] params = method.getParameterList().getParameters();
if (params.length < 1) return null; // no params
PyParameter first_parm = params[0];
PyNamedParameter first_parm = params[0].getAsNamed();
if (first_parm == null) return null;
if (first_parm.isKeywordContainer() || first_parm.isPositionalContainer()) return null; // legal but crazy cases; back off
final String first_param_name = first_parm.getName();
if (first_param_name == null || first_param_name.length() < 1) return null; // ignore cases of incorrect code
@@ -122,7 +123,7 @@ public class PyMethodFirstArgAssignmentInspection extends LocalInspectionTool {
private void markDefinition(PyElement definer) {
final String first_param_name = extractFirstParamName(definer);
if (first_param_name != null && first_param_name.equals(definer.getName())) {
complain(definer.getNode().findChildByType(PyTokenTypes.IDENTIFIER).getPsi(), first_param_name);
complain(definer.getNode().findChildByType(PyTokenTypes.IDENTIFIER).getPsi(), first_param_name); // no NPE here, or we won't have the name
}
}
@@ -103,24 +103,32 @@ public class PyMethodParametersInspection extends LocalInspectionTool {
}
}
else {
String pname = params[0].getText();
// every dup, swap, drop, or dup+drop of "self"
@NonNls String[] mangled = {"eslf", "sself", "elf", "felf", "slef", "seelf", "slf", "sslf", "sefl", "sellf", "sef", "seef"};
for (String typo : mangled) {
if (typo.equals(pname)) {
registerProblem(params[0].getNode().getPsi(), PyBundle.message("INSP.probably.mistyped.self"), new RenameToSelfQuickFix());
return;
PyNamedParameter first_param = params[0].getAsNamed();
if (first_param != null) {
String pname = first_param.getText();
// every dup, swap, drop, or dup+drop of "self"
@NonNls String[] mangled = {"eslf", "sself", "elf", "felf", "slef", "seelf", "slf", "sslf", "sefl", "sellf", "sef", "seef"};
for (String typo : mangled) {
if (typo.equals(pname)) {
registerProblem(params[0].getNode().getPsi(), PyBundle.message("INSP.probably.mistyped.self"), new RenameToSelfQuickFix());
return;
}
}
// TODO: check for style settings
if (decoratedJustWith(node, PyNames.CLASSMETHOD)) {
if (!"cls".equals(pname)) {
registerProblem(plist, PyBundle.message("INSP.usually.named.cls"));
}
}
else if (!"self".equals(pname) && !decoratedJustWith(node, PyNames.STATICMETHOD)) {
registerProblem(plist, PyBundle.message("INSP.usually.named.self"));
}
}
// TODO: check for style settings
if (decoratedJustWith(node, PyNames.CLASSMETHOD)) {
if (!"cls".equals(pname)) {
registerProblem(plist, PyBundle.message("INSP.usually.named.cls"));
else { // the unusual case of a method with first tuple param
if (!decoratedJustWith(node, PyNames.STATICMETHOD)) {
registerProblem(plist, PyBundle.message("INSP.first.param.must.not.be.tuple"));
}
}
else if (!"self".equals(pname) && !decoratedJustWith(node, PyNames.STATICMETHOD)) {
registerProblem(plist, PyBundle.message("INSP.usually.named.self"));
}
}
}
}
@@ -133,7 +133,7 @@ public class FunctionParsing extends Parsing {
myBuilder.advanceLexer();
getExpressionParser().parseSingleExpression(false);
}
parameter.done(PyElementTypes.FORMAL_PARAMETER);
parameter.done(PyElementTypes.NAMED_PARAMETER);
}
else {
myBuilder.error(message("PARSE.expected.formal.param.name"));
@@ -150,12 +150,13 @@ public class FunctionParsing extends Parsing {
private void parseParameterSubList() {
assertCurrentToken(PyTokenTypes.LPAR);
final PsiBuilder.Marker tuple = myBuilder.mark();
myBuilder.advanceLexer();
while (true) {
if (myBuilder.getTokenType() == PyTokenTypes.IDENTIFIER) {
final PsiBuilder.Marker parameter = myBuilder.mark();
myBuilder.advanceLexer();
parameter.done(PyElementTypes.FORMAL_PARAMETER);
parameter.done(PyElementTypes.NAMED_PARAMETER);
}
else if (myBuilder.getTokenType() == PyTokenTypes.LPAR) {
parseParameterSubList();
@@ -170,5 +171,6 @@ public class FunctionParsing extends Parsing {
}
myBuilder.advanceLexer();
}
tuple.done(PyElementTypes.TUPLE_PARAMETER);
}
}
@@ -36,6 +36,9 @@ public interface PyArgumentList extends PyElement {
void addArgumentFirst(PyExpression arg);
void addArgumentAfter(PyExpression argument, PyExpression afterThis);
/**
* @return the call expression to which this argument list belongs; not null in correctly parsed cases.
*/
@Nullable
PyCallExpression getCallExpression();
@@ -68,7 +71,22 @@ public interface PyArgumentList extends PyElement {
/**
* @return A mapping parameter->argument for non-starred parameters (but includes starred argument).
*/
@NotNull Map<PyExpression, PyParameter> getPlainMappedParams();
@NotNull Map<PyExpression, PyNamedParameter> getPlainMappedParams();
/**
* Consider a piece of Python 2.x code:
* <pre>
* def f(a, (b, c,), d):
* ...
*
* x = (1, 2)
* f(10, x, 20)
* </pre>
* Here, argument <tt>x</tt> successfully maps to both <tt>b</tt> and <tt>c</tt> parameters.
* This case is rare, so a separate method is introduced, to keep {@link AnalysisResult#getPlainMappedParams()} simple.
* @return mapping of arguments to nested parameters that get collectively mapped to that argument.
*/
@NotNull Map<PyExpression, List<PyNamedParameter>> getNestedMappedParams();
/**
* @return First *arg, or null.
@@ -79,7 +97,7 @@ public interface PyArgumentList extends PyElement {
/**
* @return A list of parameters mapped to a *arg.
*/
@NotNull List<PyParameter> getTupleMappedParams();
@NotNull List<PyNamedParameter> getTupleMappedParams();
/**
* @return First **arg, or null.
@@ -90,13 +108,13 @@ public interface PyArgumentList extends PyElement {
/**
* @return A list of parameters mapped to an **arg.
*/
@NotNull List<PyParameter> getKwdMappedParams();
@NotNull List<PyNamedParameter> getKwdMappedParams();
/**
* @return A list of parameters for which no arguments were found ('missing').
*/
@NotNull
List<PyParameter> getUnmappedParams();
List<PyNamedParameter> getUnmappedParams();
/**
@@ -106,7 +124,7 @@ public interface PyArgumentList extends PyElement {
Map<PyExpression, EnumSet<ArgFlag>> getArgumentFlags();
/**
* @return result of a resolveCallee() against the function call to which the paramater list belongs.
* @return result of a resolveCallee() against the function call to which the parameter list belongs.
*/
@Nullable
PyCallExpression.PyMarkedFunction getMarkedFunction();
@@ -66,5 +66,5 @@ public interface PyElementGenerator {
<T> T createFromText(final Project project, Class<T> aClass, final String text);
<T> T createFromText(final Project project, Class<T> aClass, final String text, final int[] path);
PyParameter createParameter(@NotNull final Project project, @NotNull String name);
PyNamedParameter createParameter(@NotNull final Project project, @NotNull String name);
}
@@ -0,0 +1,43 @@
/*
* Copyright 2005 Pythonid Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS"; BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.psi.stubs.PyNamedParameterStub;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a named parameter, as opposed to a tuple parameter.
*/
public interface PyNamedParameter extends PyParameter, PyElement, PsiNamedElement, PyExpression, StubBasedPsiElement<PyNamedParameterStub> {
boolean isPositionalContainer();
boolean isKeywordContainer();
@Nullable
PyExpression getDefaultValue();
/**
* @param includeDefaultValue if true, include the default value after an " = ".
* @return Canonical representation of parameter. Includes asterisks for *param and **param, and name.
*/
@NotNull
String getRepr(boolean includeDefaultValue);
}
@@ -1,47 +1,24 @@
/*
* Copyright 2005 Pythonid Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS"; BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.psi.stubs.PyParameterStub;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Created by IntelliJ IDEA.
* User: yole
* Date: 29.05.2005
* Time: 23:04:33
* To change this template use File | Settings | File Templates.
* Abstract function parameter; may cover either a named parameter or a tuple of parameters.
* @see com.jetbrains.python.psi.impl.ParamHelper
* User: dcheryasov
* Date: Jul 5, 2009 8:30:13 PM
*/
public interface PyParameter extends PyElement, PsiNamedElement, PyExpression, StubBasedPsiElement<PyParameterStub> {
boolean isPositionalContainer();
boolean isKeywordContainer();
@Nullable
PyExpression getDefaultValue();
public interface PyParameter extends PyElement {
/**
* @param includeDefaultValue if true, include the default value after an " = ".
* @return Canonical representation of parameter. Includes asterisks for *param and **param, and name.
* @return the named parameter which is represented by this parameter, or null if the parameter is a tuple.
*/
@NotNull
String getRepr(boolean includeDefaultValue);
}
@Nullable
PyNamedParameter getAsNamed();
/**
* @return the tuple parameter which is represented by this parameter, or null if the parameter is named.
*/
@Nullable
PyTupleParameter getAsTuple();
}
@@ -36,13 +36,6 @@ public interface PyParameterList extends PyElement, StubBasedPsiElement<PyParame
* Adds a paramter to list, after all other parameters.
* @param param what to add
*/
void addParameter(PyParameter param);
void addParameter(PyNamedParameter param);
/**
* Python 2.x allows for declarations like {@code def foo(a, (b, c))} that auto-unpack complex tuple parameters.
* From caller side, such functions contain a smaller number of parameters, some of them unnamed and structurally constrained.
* (This is considered evil and eschewed in Py3k.)
* @return true if the parameter list contains a tuple-based declaration.
*/
boolean containsTuples();
}
@@ -0,0 +1,17 @@
package com.jetbrains.python.psi;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.psi.stubs.PyTupleParameterStub;
import org.jetbrains.annotations.NotNull;
/**
* Tuple parameter. Defines nothing; this interface is only needed for stub creation.
*/
public interface PyTupleParameter extends PyParameter, StubBasedPsiElement<PyTupleParameterStub> {
/**
* @return the nested parameters within this tuple parameter.
*/
@NotNull
PyParameter[] getContents();
}
@@ -0,0 +1,115 @@
package com.jetbrains.python.psi.impl;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.psi.PyTupleParameter;
import java.util.ArrayList;
import java.util.List;
/**
* Parameter-related things that should not belong directly to PyParameter.
*/
public class ParamHelper {
/**
* Runs a {@link ParamWalker ParamWalker} down the array of parameters, recursively descending into tuple parameters.
* If the array is from PyParamaterList.getParameters(), parameters are visited in the order of textual appearance
* @param params where to walk
* @param walker the walker with callbacks.
*/
public static void walkDownParamArray(PyParameter[] params, ParamWalker walker) {
int last = params.length-1;
int i = 0;
for (PyParameter param : params) {
PyTupleParameter t_param = param.getAsTuple();
if (t_param != null) {
PyParameter[] nested_params = t_param.getContents();
PyTupleParameter tpar = (PyTupleParameter)param;
walker.enterTupleParameter(tpar, (i==0), (i == last));
walkDownParamArray(nested_params, walker);
walker.leaveTupleParameter(tpar, (i==0), (i == last));
}
else walker.visitNamedParameter(param.getAsNamed(), (i==0), (i == last));
i += 1;
}
}
public static interface ParamWalker {
/**
* Is called when a tuple parameter is encountered, before visiting any parameters nested in it.
* @param param the parameter
* @param first true iff it is the first in the list
* @param last true it is the last in the list
*/
void enterTupleParameter(PyTupleParameter param, boolean first, boolean last);
/**
* Is called when all nested parameters of a given tuple parameter are visited.
* @param param the parameter
* @param first true iff it is the first in the list
* @param last true it is the last in the list
*/
void leaveTupleParameter(PyTupleParameter param, boolean first, boolean last);
/**
* Is called when a named parameter is encountered.
* @param param the parameter
* @param first true iff it is the first in the list
* @param last true it is the last in the list
*/
void visitNamedParameter(PyNamedParameter param, boolean first, boolean last);
}
public static abstract class ParamVisitor implements ParamWalker {
public void enterTupleParameter(PyTupleParameter param, boolean first, boolean last) { }
public void leaveTupleParameter(PyTupleParameter param, boolean first, boolean last) { }
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) { }
}
public static StringBuilder appendParameterList(PyParameterList plist, final StringBuilder target) {
final String COMMA = ", ";
target.append("(");
walkDownParamArray(
plist.getParameters(),
new ParamWalker() {
public void enterTupleParameter(PyTupleParameter param, boolean first, boolean last) {
target.append("(");
}
public void leaveTupleParameter(PyTupleParameter param, boolean first, boolean last) {
target.append(")");
if (! last) target.append(COMMA);
}
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
target.append(param.getRepr(true));
if (! last) target.append(COMMA);
}
}
);
target.append(")");
return target;
}
public static List<PyNamedParameter> collectNamedParameters(PyParameterList plist) {
final List<PyNamedParameter> result = new ArrayList<PyNamedParameter>(10); // a random 'enough'
walkDownParamArray(
plist.getParameters(),
new ParamWalker() {
public void enterTupleParameter(PyTupleParameter param, boolean first, boolean last) { }
public void leaveTupleParameter(PyTupleParameter param, boolean first, boolean last) { }
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
result.add(param);
}
}
);
return result;
}
}
@@ -189,7 +189,7 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
}
}
if (!good) {
throw new IllegalArgumentException("Expression " + afterThis + " is not an argument (" + getArguments() + ")");
throw new IllegalArgumentException("Expression " + afterThis + " is not an argument (" + Arrays.toString(getArguments()) + ")");
}
// CASES:
ASTNode node = afterThis.getNode().getTreeNext();
@@ -247,10 +247,11 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
}
public AnalysisResult analyzeCall() {
AnalysisResultImpl ret = new AnalysisResultImpl();
final AnalysisResultImpl ret = new AnalysisResultImpl();
PyExpression[] arguments = getArguments();
// declaration-based checks
// proper arglist is: [positional,...][name=value,...][*tuple,][**dict]
// where "positional" may be a tuple of nested "positional" parameters, too.
// following the spec: http://docs.python.org/ref/calls.html
PyCallExpression call = getCallExpression();
if (call != null) {
@@ -259,21 +260,24 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
if (resolved_callee != null) {
PyFunction func = resolved_callee.getFunction();
PyParameterList paramlist = func.getParameterList();
if (paramlist.containsTuples()) return null; // NOTE: we're not ready to handle this yet.
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>();
PyParameter kwd_slot = null; // the *tuple. might be just boolean, but this way debugging is easier
PyParameter tuple_slot = null; // the **kwd
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) {
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);
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) {
@@ -310,19 +314,28 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
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;
final PyExpression arg = unmatched_arg_iter.next(); // current arg
PyParameter a_param = params[param_index]; // its matching param
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;
}
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 { // tuple: it may contain only positionals or other tuples.
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
}
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;
}
@@ -390,47 +403,56 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
}
}
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);
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.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);
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.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);
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.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, n_param);
}
}
}
}
@@ -445,26 +467,32 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
}
// 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 param : params) {
final String param_name = 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);
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 param : params) {
final String param_name = 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);
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);
}
}
}
}
@@ -482,48 +510,122 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
ret.markArgument(arg, ArgFlag.IS_UNMAPPED);
}
// any params still unfilled?
for (final PyParameter param : unfilled_params.values()) {
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 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);
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);
}
else ret.my_plain_mapped_params.put(arg, 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));
}
}
}
return ret;
}
private static class MyParamVisitor extends PyElementVisitor {
private Iterator<PyExpression> myArgIterator;
private AnalysisResultImpl myResult;
private List<PyExpression> myUnmatchedSubargs;
protected /*static*/ class AnalysisResultImpl implements AnalysisResult {
private MyParamVisitor(Iterator<PyExpression> arg_iterator, AnalysisResultImpl ret) {
myArgIterator = arg_iterator;
myResult = ret;
myUnmatchedSubargs = new ArrayList<PyExpression>(5); // arbitrary 'enough'
}
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;
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 Map<PyExpression, PyNamedParameter> my_plain_mapped_params; // one param per arg
private 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 List<PyNamedParameter> my_tuple_mapped_params; // params mapped to *arg
private List<PyNamedParameter> my_kwd_mapped_params; // params mapped to **arg
private List<PyNamedParameter> my_unmapped_params;
private Map<PyExpression, EnumSet<PyArgumentList.ArgFlag>> my_arg_flags; // flags of every arg
private 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_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;
}
@@ -531,10 +633,15 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
/**
* @return A mapping argument->parameter for non-starred arguments (but includes starred parameters).
*/
public @NotNull Map<PyExpression, PyParameter> getPlainMappedParams() {
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.
*/
@@ -545,7 +652,7 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
/**
* @return A list of parameters mapped to an *arg.
*/
public @NotNull List<PyParameter> getTupleMappedParams(){
public @NotNull List<PyNamedParameter> getTupleMappedParams(){
return my_tuple_mapped_params;
}
@@ -559,7 +666,7 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
/**
* @return A list of parameters mapped to an **arg.
*/
public @NotNull List<PyParameter> getKwdMappedParams(){
public @NotNull List<PyNamedParameter> getKwdMappedParams(){
return my_kwd_mapped_params;
}
@@ -567,7 +674,7 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
* @return A list of parameters for which no arguments were found ('missing').
*/
public @NotNull
List<PyParameter> getUnmappedParams(){
List<PyNamedParameter> getUnmappedParams(){
return my_unmapped_params;
}
@@ -276,7 +276,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
}
final List<PyTargetExpression> result = new ArrayList<PyTargetExpression>();
// NOTE: maybe treeCrawlUp would be more precise, but currently it works well enough to not care.
// NOTE: maybe treeCrawlUp would be more precise, but currently it works well enough; don't care.
initMethod.getStatementList().accept(new PyRecursiveElementVisitor() {
public void visitPyAssignmentStatement(final PyAssignmentStatement node) {
super.visitPyAssignmentStatement(node);
@@ -261,7 +261,7 @@ public class PyElementGeneratorImpl implements PyElementGenerator {
static int[] PATH_PARAMETER = {0, 3, 1};
public PyParameter createParameter(@NotNull final Project project, @NotNull String name) {
return createFromText(project, PyParameter.class, "def f("+name+"): pass", PATH_PARAMETER);
public PyNamedParameter createParameter(@NotNull final Project project, @NotNull String name) {
return createFromText(project, PyNamedParameter.class, "def f("+name+"): pass", PATH_PARAMETER);
}
}
@@ -35,11 +35,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* Created by IntelliJ IDEA.
* User: yole
* Date: 29.05.2005
* Time: 23:01:25
* To change this template use File | Settings | File Templates.
* Implements PyFunction.
*/
public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> implements PyFunction {
public PyFunctionImpl(ASTNode astNode) {
@@ -127,8 +123,8 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
{
/*
if (lastParent != null && lastParent.getParent() == this) {
final PyParameter[] params = getParameterList().getParameters();
for (PyParameter param : params) {
final PyNamedParameter[] params = getParameterList().getParameters();
for (PyNamedParameter param : params) {
if (!processor.execute(param, substitutor)) return false;
}
}
@@ -171,4 +167,8 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
return false;
}
@Override
public String toString() {
return super.toString() + "('" + getName() + "')";
}
}
@@ -21,10 +21,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyLambdaExpression;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
@@ -53,11 +50,25 @@ public class PyLambdaExpressionImpl extends PyElementImpl implements PyLambdaExp
}
public boolean processDeclarations(@NotNull final PsiScopeProcessor processor, @NotNull final ResolveState state, final PsiElement lastParent,
@NotNull final PsiElement place) {
@NotNull final PsiElement place)
{
// TODO: move it to PyParamList
PyParameter[] parameters = getParameterList().getParameters();
return processParamLayer(parameters, processor, state, lastParent);
}
private boolean processParamLayer(
@NotNull final PyParameter[] parameters,
@NotNull final PsiScopeProcessor processor, @NotNull final ResolveState state, final PsiElement lastParent
) {
for(PyParameter param: parameters) {
if (param == lastParent) continue;
if (!processor.execute(param, state)) return false;
PyTupleParameter t_param = param.getAsTuple();
if (t_param != null) {
PyParameter[] nested_params = t_param.getContents();
if (! processParamLayer(nested_params, processor, state, lastParent)) return false;
}
else if (!processor.execute(param, state)) return false;
}
return true;
}
@@ -24,7 +24,7 @@ import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.stubs.PyParameterStub;
import com.jetbrains.python.psi.stubs.PyNamedParameterStub;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
@@ -39,19 +39,19 @@ import javax.swing.*;
* Time: 23:04:59
* To change this template use File | Settings | File Templates.
*/
public class PyParameterImpl extends PyPresentableElementImpl<PyParameterStub> implements PyParameter {
public PyParameterImpl(ASTNode astNode) {
public class PyNamedParameterImpl extends PyPresentableElementImpl<PyNamedParameterStub> implements PyNamedParameter {
public PyNamedParameterImpl(ASTNode astNode) {
super(astNode);
}
public PyParameterImpl(final PyParameterStub stub) {
super(stub, PyElementTypes.FORMAL_PARAMETER);
public PyNamedParameterImpl(final PyNamedParameterStub stub) {
super(stub, PyElementTypes.NAMED_PARAMETER);
}
@Nullable
@Override
public String getName() {
final PyParameterStub stub = getStub();
final PyNamedParameterStub stub = getStub();
if (stub != null) {
return stub.getName();
}
@@ -73,7 +73,7 @@ public class PyParameterImpl extends PyPresentableElementImpl<PyParameterStub> i
}
public boolean isPositionalContainer() {
final PyParameterStub stub = getStub();
final PyNamedParameterStub stub = getStub();
if (stub != null) {
return stub.isPositionalContainer();
}
@@ -83,7 +83,7 @@ public class PyParameterImpl extends PyPresentableElementImpl<PyParameterStub> i
}
public boolean isKeywordContainer() {
final PyParameterStub stub = getStub();
final PyNamedParameterStub stub = getStub();
if (stub != null) {
return stub.isKeywordContainer();
}
@@ -119,6 +119,14 @@ public class PyParameterImpl extends PyPresentableElementImpl<PyParameterStub> i
return Icons.PARAMETER_ICON;
}
public PyNamedParameter getAsNamed() {
return this;
}
public PyTupleParameter getAsTuple() {
return null; // we're not a tuple
}
public PyType getType() {
if (getParent() instanceof PyParameterList) {
PyParameterList parameterList = (PyParameterList) getParent();
@@ -139,4 +147,9 @@ public class PyParameterImpl extends PyPresentableElementImpl<PyParameterStub> i
}
return null;
}
@Override
public String toString() {
return super.toString() + "('" + getName() + "')";
}
}
@@ -19,7 +19,6 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.stubs.PyParameterListStub;
import org.jetbrains.annotations.NotNull;
@@ -46,38 +45,19 @@ public class PyParameterListImpl extends PyBaseElementImpl<PyParameterListStub>
}
public PyParameter[] getParameters() {
return getStubOrPsiChildren(PyElementTypes.FORMAL_PARAMETER, new PyParameter[0]);
return getStubOrPsiChildren(PyElementTypes.PARAMETERS, new PyParameter[0]);
}
public void addParameter(final PyParameter param) {
public void addParameter(final PyNamedParameter param) {
PsiElement paren = getLastChild();
if (paren != null && ")".equals(paren.getText())) {
PyUtil.ensureWritable(this);
ASTNode beforeWhat = paren.getNode(); // the closing bracket will be this
ASTNode beforeWhat = paren.getNode(); // the closing paren will be this
PyParameter[] params = getParameters();
PyUtil.addListNode(this, param, beforeWhat, true, params.length == 0);
}
}
public boolean containsTuples() {
// look for an opening paren after first paren. parser makes no efort to sort them; maybe should be fixed someday.
// find opening LPAR; some whitespace may come first
PsiElement seeker;
for (seeker = getFirstChild(); seeker != null; seeker = seeker.getNextSibling()) { // C invented this flexible construct, why not use it?
ASTNode node = seeker.getNode();
if (node != null && node.getElementType() == PyTokenTypes.LPAR) break;
}
// an LPAR inside
while (seeker != null) {
seeker = seeker.getNextSibling(); // step over the LPAR we just saw
if (seeker != null) {
ASTNode node = seeker.getNode();
if (node != null && node.getElementType() == PyTokenTypes.LPAR) return true;
}
}
return false;
}
@NotNull
public Iterable<PyElement> iterateNames() {
return new ArrayIterable<PyElement>(getParameters());
@@ -339,9 +339,9 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
* Resolves reference to possible referred elements.
* First element is always what resolve() would return.
* Imported module names: to module file, or {directory, '__init__.py}' for a qualifier.
* @todo Local identifiers: a list of definitions in the most recent compound statement
* todo Local identifiers: a list of definitions in the most recent compound statement
* (e.g. <code>if X: a = 1; else: a = 2</code> has two definitions of <code>a</code>.).
* @todo Identifiers not found locally: similar definitions in imported files and builtins.
* todo Identifiers not found locally: similar definitions in imported files and builtins.
* @see com.intellij.psi.PsiPolyVariantReference#multiResolve(boolean)
**/
@NotNull
@@ -419,8 +419,8 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
return ResolveImportUtil.suggestImportVariants(this);
}
List<Object> ret = new ArrayList<Object>();
LookupElementFactory factory = LookupElementFactory.getInstance();
final List<Object> ret = new ArrayList<Object>();
final LookupElementFactory factory = LookupElementFactory.getInstance();
// include our own names
final VariantsProcessor processor = new VariantsProcessor();
@@ -433,13 +433,20 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
if (callee instanceof PyReferenceExpression) {
PsiElement def = ((PyReferenceExpression)callee).resolve();
if (def instanceof PyFunction) {
for (PyParameter param : ((PyFunction)def).getParameterList().getParameters()) {
if (! param.isKeywordContainer() && ! param.isPositionalContainer()) {
LookupItem item = (LookupItem)factory.createLookupElement(param.getName() + "=");
item.setIcon(param.getIcon(0));
ret.add(item);
((PyFunction)def).getParameterList().accept(
new PyElementVisitor() {
@Override
public void visitPyParameter(PyParameter par) {
PyNamedParameter n_param = par.getAsNamed();
assert n_param != null;
if (! n_param.isKeywordContainer() && ! n_param.isPositionalContainer()) {
LookupItem item = (LookupItem)factory.createLookupElement(n_param.getName() + "=");
item.setIcon(n_param.getIcon(0));
ret.add(item);
}
}
}
}
);
}
}
}
@@ -517,9 +524,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
}
}
if (this == place) {
return true;
}
if (this == place) return true;
return processor.execute(this, substitutor);
}
@@ -0,0 +1,54 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyTupleParameter;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.stubs.PyTupleParameterStub;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/**
* Represents a tuple parameter as stubbed element.
*/
public class PyTupleParameterImpl extends PyPresentableElementImpl<PyTupleParameterStub> implements PyTupleParameter {
public PyTupleParameterImpl(ASTNode astNode) {
super(astNode);
}
protected PyTupleParameterImpl(final PyTupleParameterStub stub, final IStubElementType nodeType) {
super(stub, nodeType);
}
public PyTupleParameterImpl(PyTupleParameterStub stub) {
super(stub, PyElementTypes.TUPLE_PARAMETER);
}
public PyNamedParameter getAsNamed() {
return null; // we're not named
}
public PyTupleParameter getAsTuple() {
return this;
}
public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
throw new IncorrectOperationException("Can't rename a tuple parameter to '" + name +"'");
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyParameter(this);
}
@NotNull
public PyParameter[] getContents() {
return getStubOrPsiChildren(PyElementTypes.PARAMETERS, new PyParameter[0]);
}
}
@@ -2,7 +2,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.Nullable;
@@ -17,5 +17,5 @@ public interface PyTypeProvider {
PyType getReferenceType(PsiElement referenceTarget);
@Nullable
PyType getParameterType(PyParameter param, final PyFunction func);
PyType getParameterType(PyNamedParameter param, final PyFunction func);
}
@@ -1,51 +0,0 @@
/*
* @author max
*/
package com.jetbrains.python.psi.impl.stubs;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubOutputStream;
import com.intellij.psi.stubs.StubInputStream;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.PersistentStringEnumerator;
import com.intellij.util.io.StringRef;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyStubElementType;
import com.jetbrains.python.psi.impl.PyParameterImpl;
import com.jetbrains.python.psi.stubs.PyParameterStub;
import java.io.IOException;
public class PyFormalParameterElementType extends PyStubElementType<PyParameterStub, PyParameter> {
public PyFormalParameterElementType() {
super("FORMAL_PARAMETER");
}
public PyParameter createPsi(final PyParameterStub stub) {
return new PyParameterImpl(stub);
}
public PyParameterStub createStub(final PyParameter psi, final StubElement parentStub) {
return new PyParameterStubImpl(psi.getName(), psi.isPositionalContainer(), psi.isKeywordContainer(), parentStub);
}
public PsiElement createElement(final ASTNode node) {
return new PyParameterImpl(node);
}
public void serialize(final PyParameterStub stub, final StubOutputStream dataStream)
throws IOException {
dataStream.writeName(stub.getName());
dataStream.writeBoolean(stub.isKeywordContainer());
dataStream.writeBoolean(stub.isPositionalContainer());
}
public PyParameterStub deserialize(final StubInputStream dataStream, final StubElement parentStub) throws IOException {
String name = StringRef.toString(dataStream.readName());
boolean keyword = dataStream.readBoolean();
boolean positional = dataStream.readBoolean();
return new PyParameterStubImpl(name, positional, keyword, parentStub);
}
}
@@ -0,0 +1,49 @@
/*
* @author max
*/
package com.jetbrains.python.psi.impl.stubs;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubOutputStream;
import com.intellij.psi.stubs.StubInputStream;
import com.intellij.util.io.StringRef;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyStubElementType;
import com.jetbrains.python.psi.impl.PyNamedParameterImpl;
import com.jetbrains.python.psi.stubs.PyNamedParameterStub;
import java.io.IOException;
public class PyNamedParameterElementType extends PyStubElementType<PyNamedParameterStub, PyNamedParameter> {
public PyNamedParameterElementType() {
super("NAMED_PARAMETER");
}
public PyNamedParameter createPsi(final PyNamedParameterStub stub) {
return new PyNamedParameterImpl(stub);
}
public PyNamedParameterStub createStub(final PyNamedParameter psi, final StubElement parentStub) {
return new PyNamedParameterStubImpl(psi.getName(), psi.isPositionalContainer(), psi.isKeywordContainer(), parentStub);
}
public PsiElement createElement(final ASTNode node) {
return new PyNamedParameterImpl(node);
}
public void serialize(final PyNamedParameterStub stub, final StubOutputStream dataStream)
throws IOException {
dataStream.writeName(stub.getName());
dataStream.writeBoolean(stub.isKeywordContainer());
dataStream.writeBoolean(stub.isPositionalContainer());
}
public PyNamedParameterStub deserialize(final StubInputStream dataStream, final StubElement parentStub) throws IOException {
String name = StringRef.toString(dataStream.readName());
boolean keyword = dataStream.readBoolean();
boolean positional = dataStream.readBoolean();
return new PyNamedParameterStubImpl(name, positional, keyword, parentStub);
}
}
@@ -6,16 +6,16 @@ package com.jetbrains.python.psi.impl.stubs;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.stubs.PyParameterStub;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.stubs.PyNamedParameterStub;
public class PyParameterStubImpl extends StubBase<PyParameter> implements PyParameterStub {
public class PyNamedParameterStubImpl extends StubBase<PyNamedParameter> implements PyNamedParameterStub {
private final String myName;
private final boolean myPositionalContainer;
private final boolean myKeywordContainer;
public PyParameterStubImpl(String name, boolean isPositionalContainer, boolean isKeywordContainer, StubElement parent) {
super(parent, PyElementTypes.FORMAL_PARAMETER);
public PyNamedParameterStubImpl(String name, boolean isPositionalContainer, boolean isKeywordContainer, StubElement parent) {
super(parent, PyElementTypes.NAMED_PARAMETER);
myName = name;
myPositionalContainer = isPositionalContainer;
@@ -0,0 +1,43 @@
package com.jetbrains.python.psi.impl.stubs;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubInputStream;
import com.intellij.psi.stubs.StubOutputStream;
import com.jetbrains.python.psi.PyStubElementType;
import com.jetbrains.python.psi.PyTupleParameter;
import com.jetbrains.python.psi.impl.PyTupleParameterImpl;
import com.jetbrains.python.psi.stubs.PyTupleParameterStub;
import java.io.IOException;
/**
* Does actual storing and loading of tuple parameter stub. Not much to do.
*/
public class PyTupleParameterElementType extends PyStubElementType<PyTupleParameterStub, PyTupleParameter> {
public PyTupleParameterElementType() {
super("TUPLE_PARAMETER");
}
public PsiElement createElement(ASTNode node) {
return new PyTupleParameterImpl(node);
}
public PyTupleParameter createPsi(PyTupleParameterStub stub) {
return new PyTupleParameterImpl(stub);
}
public PyTupleParameterStub createStub(PyTupleParameter psi, StubElement parentStub) {
return new PyTupleParameterStubImpl(parentStub);
}
public PyTupleParameterStub deserialize(StubInputStream dataStream, StubElement parentStub) throws IOException {
return new PyTupleParameterStubImpl(parentStub);
}
public void serialize(PyTupleParameterStub stub, StubOutputStream dataStream) throws IOException {
// nothing; children serialize themselves
}
}
@@ -0,0 +1,20 @@
package com.jetbrains.python.psi.impl.stubs;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyTupleParameter;
import com.jetbrains.python.psi.stubs.PyTupleParameterStub;
/**
* Implementation does nothing but marking the element type.
* User: dcheryasov
* Date: Jul 6, 2009 1:33:08 AM
*/
public class PyTupleParameterStubImpl extends StubBase<PyTupleParameter> implements PyTupleParameterStub {
protected PyTupleParameterStubImpl(StubElement parent) {
super(parent, PyElementTypes.TUPLE_PARAMETER);
}
}
@@ -4,9 +4,9 @@
package com.jetbrains.python.psi.stubs;
import com.intellij.psi.stubs.NamedStub;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyNamedParameter;
public interface PyParameterStub extends NamedStub<PyParameter> {
public interface PyNamedParameterStub extends NamedStub<PyNamedParameter> {
boolean isPositionalContainer();
boolean isKeywordContainer();
}
@@ -0,0 +1,10 @@
package com.jetbrains.python.psi.stubs;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.psi.PyTupleParameter;
/**
* Tuple parameter stub, collects nested parameters from stubs.
*/
public interface PyTupleParameterStub extends StubElement<PyTupleParameter> {
}
@@ -22,13 +22,13 @@ import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.util.Iconable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.LayeredIcon;
import com.intellij.util.Icons;
import com.jetbrains.python.PyIcons;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import org.jetbrains.annotations.Nullable;
@@ -43,175 +43,169 @@ import java.util.Set;
*/
public class PyStructureViewElement implements StructureViewTreeElement {
private enum Visibility {
NORMAL, // visible
INVISIBLE, // not visible: e.g. local to function
PRIVATE, // "__foo" in a class
PREDEFINED // like "__init__"; only if really visible
}
private PyElement my_element;
private Visibility my_visibility;
private Icon my_icon;
private enum Visibility {
NORMAL, // visible
INVISIBLE, // not visible: e.g. local to function
PRIVATE, // "__foo" in a class
PREDEFINED // like "__init__"; only if really visible
}
public PyStructureViewElement(PyElement element, Visibility vis) {
my_element = element;
my_visibility = vis;
}
private PyElement my_element;
private Visibility my_visibility;
private Icon my_icon;
public PyStructureViewElement(PyElement element) {
this(element, Visibility.NORMAL);
}
public PyElement getValue() {
return my_element;
}
public PyStructureViewElement(PyElement element, Visibility vis) {
my_element = element;
my_visibility = vis;
}
public void navigate(boolean requestFocus) {
((NavigationItem)my_element).navigate(requestFocus);
}
public PyStructureViewElement(PyElement element) {
this(element, Visibility.NORMAL);
}
public boolean canNavigate() {
return ((NavigationItem)my_element).canNavigate();
}
public PyElement getValue() {
return my_element;
}
public boolean canNavigateToSource() {
return ((NavigationItem)my_element).canNavigateToSource();
}
public void navigate(boolean requestFocus) {
((NavigationItem)my_element).navigate(requestFocus);
}
public void setIcon(Icon icon) {
my_icon = icon;
}
public boolean canNavigate() {
return ((NavigationItem)my_element).canNavigate();
}
public StructureViewTreeElement[] getChildren() {
final Set<PyElement> childrenElements = new HashSet<PyElement>();
my_element.acceptChildren(new PyElementVisitor() {
@Override public void visitElement(PsiElement element) {
if (isWorthyClassItem(element)) {
childrenElements.add((PyElement)element);
}
else {
element.acceptChildren(this);
}
}
public boolean canNavigateToSource() {
return ((NavigationItem)my_element).canNavigateToSource();
}
@Override public void visitPyParameter(final PyParameter node) {
// Do not add parameters to structure view
}
});
public void setIcon(Icon icon) {
my_icon = icon;
}
StructureViewTreeElement[] children = new StructureViewTreeElement[childrenElements.size()];
int i = 0;
for (PyElement element : childrenElements) {
// look at functions and predefined __names__
Visibility vis = Visibility.NORMAL;
if (PsiTreeUtil.getParentOfType(element, PyFunction.class) != null) {
// whatever is defined inside a def, is hidden
vis = Visibility.INVISIBLE;
public StructureViewTreeElement[] getChildren() {
final Set<PyElement> childrenElements = new HashSet<PyElement>();
my_element.acceptChildren(new PyElementVisitor() {
@Override
public void visitElement(PsiElement element) {
if (isWorthyClassItem(element)) {
childrenElements.add((PyElement)element);
}
else {
element.acceptChildren(this);
}
}
public void visitPyParameter(final PyNamedParameter node) {
// Do not add parameters to structure view
}
});
StructureViewTreeElement[] children = new StructureViewTreeElement[childrenElements.size()];
int i = 0;
for (PyElement element : childrenElements) {
// look at functions and predefined __names__
Visibility vis = Visibility.NORMAL;
if (PsiTreeUtil.getParentOfType(element, PyFunction.class) != null) {
// whatever is defined inside a def, is hidden
vis = Visibility.INVISIBLE;
}
else {
String name = element.getName();
if (name != null && name.startsWith("__")) {
if (PyNames.UnderscoredNames.contains(name)) {
vis = Visibility.PREDEFINED;
}
else {
String name = element.getName();
if (name != null && name.startsWith("__")) {
if (PyNames.UnderscoredNames.contains(name)) vis = Visibility.PREDEFINED;
else vis = Visibility.PRIVATE;
}
}
children[i] = new PyStructureViewElement(element, vis);
if (element instanceof PyClass) {
PyClass the_exception = PyBuiltinCache.getInstance(element.getProject()).getClass("Exception");
final PyClass cls = (PyClass)element;
for (PyClass anc : cls.iterateAncestors()) {
if (anc == the_exception) {
((PyStructureViewElement)(children[i])).setIcon(Icons.EXCEPTION_CLASS_ICON);
break;
}
}
}
i += 1;
}
return children;
}
static boolean isWorthyClassItem(PsiElement element) {
if (element instanceof PyClass) return true;
if (element instanceof PyFunction) return true;
if ((element instanceof PyTargetExpression) && ((PyTargetExpression)element).getQualifier() == null) {
PsiElement e = element.getParent();
if (e instanceof PyAssignmentStatement) {
e = e.getParent();
if (e instanceof PyStatementList) {
e = e.getParent();
if (e instanceof PyClass) {
return true;
}
vis = Visibility.PRIVATE;
}
}
}
return false;
children[i] = new PyStructureViewElement(element, vis);
if (element instanceof PyClass) {
PyClass the_exception = PyBuiltinCache.getInstance(element.getProject()).getClass("Exception");
final PyClass cls = (PyClass)element;
for (PyClass anc : cls.iterateAncestors()) {
if (anc == the_exception) {
((PyStructureViewElement)(children[i])).setIcon(Icons.EXCEPTION_CLASS_ICON);
break;
}
}
}
i += 1;
}
public ItemPresentation getPresentation() {
return new ItemPresentation() {
public String getPresentableText() {
if (my_element instanceof PyFunction) {
PsiElement[] children = my_element.getChildren();
if (children.length > 0 && children [0] instanceof PyParameterList) {
PyParameterList argList = (PyParameterList) children [0];
StringBuilder result = new StringBuilder(((PsiNamedElement)my_element).getName());
// TODO: reuse logic from PyParameterInfoHandler
result.append("(");
boolean first = true;
for(PsiElement e: argList.getChildren()) {
if (e instanceof PyParameter) {
if (first) {
first = false;
}
else {
result.append(",");
}
PyParameter p = (PyParameter) e;
if (p.isPositionalContainer()) {
result.append("*");
}
else if (p.isKeywordContainer()) {
result.append("**");
}
result.append(p.getName());
}
}
result.append(")");
return result.toString();
}
}
return ((PsiNamedElement)my_element).getName();
}
return children;
}
public @Nullable TextAttributesKey getTextAttributesKey() {
return null;
}
public @Nullable String getLocationString() {
return null;
}
public Icon getIcon(boolean open) {
Icon normal_icon = my_element.getIcon(Iconable.ICON_FLAG_OPEN);
if (my_icon != null) normal_icon = my_icon; // override normal
if (my_visibility == Visibility.NORMAL) {
return normal_icon;
}
else {
LayeredIcon icon = new LayeredIcon(2);
icon.setIcon(normal_icon, 0);
Icon overlay = null;
if (my_visibility == Visibility.PRIVATE) overlay = PyIcons.PRIVATE;
else if (my_visibility == Visibility.PREDEFINED) overlay = PyIcons.PREDEFINED;
else if (my_visibility == Visibility.INVISIBLE) overlay = PyIcons.INVISIBLE;
if (overlay != null) {
icon.setIcon(overlay, 1);
}
return icon;
}
}
};
static boolean isWorthyClassItem(PsiElement element) {
if (element instanceof PyClass) return true;
if (element instanceof PyFunction) return true;
if ((element instanceof PyTargetExpression) && ((PyTargetExpression)element).getQualifier() == null) {
PsiElement e = element.getParent();
if (e instanceof PyAssignmentStatement) {
e = e.getParent();
if (e instanceof PyStatementList) {
e = e.getParent();
if (e instanceof PyClass) {
return true;
}
}
}
}
return false;
}
public ItemPresentation getPresentation() {
return new ItemPresentation() {
public String getPresentableText() {
if (my_element instanceof PyFunction) {
PsiElement[] children = my_element.getChildren();
if (children.length > 0 && children[0] instanceof PyParameterList) {
PyParameterList argList = (PyParameterList)children[0];
StringBuilder result = new StringBuilder(my_element.getName());
ParamHelper.appendParameterList(argList, result);
return result.toString();
}
}
return my_element.getName();
}
public
@Nullable
TextAttributesKey getTextAttributesKey() {
return null;
}
public
@Nullable
String getLocationString() {
return null;
}
public Icon getIcon(boolean open) {
Icon normal_icon = my_element.getIcon(Iconable.ICON_FLAG_OPEN);
if (my_icon != null) normal_icon = my_icon; // override normal
if (my_visibility == Visibility.NORMAL) {
return normal_icon;
}
else {
LayeredIcon icon = new LayeredIcon(2);
icon.setIcon(normal_icon, 0);
Icon overlay = null;
if (my_visibility == Visibility.PRIVATE) {
overlay = PyIcons.PRIVATE;
}
else if (my_visibility == Visibility.PREDEFINED) {
overlay = PyIcons.PREDEFINED;
}
else if (my_visibility == Visibility.INVISIBLE) overlay = PyIcons.INVISIBLE;
if (overlay != null) {
icon.setIcon(overlay, 1);
}
return icon;
}
}
};
}
}
@@ -16,40 +16,50 @@
package com.jetbrains.python.validation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import java.util.HashSet;
import java.util.Set;
/**
* Created by IntelliJ IDEA.
* User: yole
* Date: 13.06.2005
* Time: 15:33:24
* To change this template use File | Settings | File Templates.
* Annotates errors in 'global' statements.
*/
public class GlobalAnnotator extends PyAnnotator {
@Override public void visitPyGlobalStatement(final PyGlobalStatement node) {
PyFunction function = node.getContainingElement(PyFunction.class);
if (function != null) {
PyParameterList paramList = function.getParameterList();
PyParameter[] params = paramList.getParameters();
Set<String> paramNames = new HashSet<String>();
for (PyParameter param: params) {
paramNames.add(param.getName());
}
for (PyReferenceExpression expr: node.getGlobals()) {
if (paramNames.contains(expr.getReferencedName())) {
getHolder().createErrorAnnotation(expr.getTextRange(), "name is used as both global and parameter");
}
PsiElement resolvedElement = expr.resolve();
if (resolvedElement != null && PsiTreeUtil.isAncestor(function, resolvedElement, true)) {
getHolder().createWarningAnnotation(expr.getTextRange(), "name '" + expr.getReferencedName() +
"' is assigned to before global declaration");
}
}
@Override
public void visitPyGlobalStatement(final PyGlobalStatement node) {
PyFunction function = node.getContainingElement(PyFunction.class);
if (function != null) {
PyParameterList paramList = function.getParameterList();
// collect param names
final Set<String> paramNames = new HashSet<String>();
ParamHelper.walkDownParamArray(
paramList.getParameters(),
new ParamHelper.ParamVisitor() {
@Override
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
paramNames.add(param.getName());
}
}
);
// check globals
final AnnotationHolder holder = getHolder();
for (PyReferenceExpression expr : node.getGlobals()) {
final String expr_name = expr.getReferencedName();
if (paramNames.contains(expr_name)) {
holder.createErrorAnnotation(expr.getTextRange(), PyBundle.message("ANN.$0.both.global.and.param", expr_name));
}
PsiElement resolvedElement = expr.resolve();
if (resolvedElement != null && PsiTreeUtil.isAncestor(function, resolvedElement, true)) {
getHolder().createWarningAnnotation(expr.getTextRange(),PyBundle.message("ANN.$0.both.global.and.param", expr_name));
}
}
}
}
}
@@ -17,47 +17,55 @@
package com.jetbrains.python.validation;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.psi.impl.ParamHelper;
import java.util.Set;
/**
* Checks for anomalies in parameter lists of function declarations.
* User: yole
* Date: 12.06.2005
*/
public class ParameterListAnnotator extends PyAnnotator {
@Override public void visitPyParameterList(final PyParameterList node) {
HashSet<String> parameterNames = new HashSet<String>();
PyParameter[] parameters = node.getParameters();
@Override
public void visitPyParameterList(final PyParameterList paramlist) {
ParamHelper.walkDownParamArray(
paramlist.getParameters(),
new ParamHelper.ParamVisitor() {
Set<String> parameterNames = new HashSet<String>();
boolean hadPositionalContainer = false, hadKeywordContainer = false;
boolean hadDefaultValue = false;
for (PyParameter parameter: parameters) {
if (parameterNames.contains(parameter.getName())) {
getHolder().createErrorAnnotation(parameter, "duplicate parameter name");
@Override
public void visitNamedParameter(PyNamedParameter parameter, boolean first, boolean last) {
if (parameterNames.contains(parameter.getName())) {
getHolder().createErrorAnnotation(parameter, PyBundle.message("ANN.duplicate.param.name"));
}
parameterNames.add(parameter.getName());
if (parameter.isPositionalContainer()) {
if (hadKeywordContainer) {
getHolder().createErrorAnnotation(parameter, PyBundle.message("ANN.starred.param.after.kwparam"));
}
parameterNames.add(parameter.getName());
if (parameter.isPositionalContainer()) {
if (hadKeywordContainer) {
getHolder().createErrorAnnotation(parameter, "* parameter after ** paremeter");
}
hadPositionalContainer = true;
hadPositionalContainer = true;
}
else if (parameter.isKeywordContainer()) {
hadKeywordContainer = true;
}
else {
if (hadPositionalContainer || hadKeywordContainer) {
getHolder().createErrorAnnotation(parameter, PyBundle.message("ANN.regular.param.after.starred"));
}
else if (parameter.isKeywordContainer()) {
hadKeywordContainer = true;
if (parameter.getDefaultValue() != null) {
hadDefaultValue = true;
}
else {
if (hadPositionalContainer || hadKeywordContainer) {
getHolder().createErrorAnnotation(parameter, "regular parameter after * or ** parameter");
}
if (parameter.getDefaultValue() != null) {
hadDefaultValue = true;
}
else {
if (hadDefaultValue) {
getHolder().createErrorAnnotation(parameter, "non-default parameter follows default parameter");
}
}
if (hadDefaultValue) {
getHolder().createErrorAnnotation(parameter, PyBundle.message("ANN.non.default.param.after.default"));
}
}
}
}
}
}
);
}
}
@@ -0,0 +1,5 @@
# py 2.x only
def foo(a, (b, (c, d)), e):
pass
foo(<arg1>1, (<arg2>2, (<arg3>3, <arg4>4)), <arg5>5)
+4
View File
@@ -0,0 +1,4 @@
def foo(a, b, c):
pass
foo(<arg1>1, <arg2>**{'b':2, <arg2a>'c':3})
@@ -0,0 +1,4 @@
def foo(a, b, c):
pass
foo(<arg1>b=2, <arg2>**{'a':1, <arg2a>'c':3})
+5
View File
@@ -0,0 +1,5 @@
def foo(a, b, **c):
pass
foo(<arg1>1, <arg2>2, <arg3>x=3, <arg4>**{'y':4})
@@ -0,0 +1,5 @@
def foo(a, b, **c):
pass
foo(<arg1>z=1, <arg2>b=2, <arg3>a=3, <arg4>**{'y':4})
+5
View File
@@ -0,0 +1,5 @@
# py 2.x only
def foo(a, (b, c), d):
pass
foo(<arg1>1, (<arg2>2, <arg3>3), <arg4>4)
@@ -0,0 +1,5 @@
# py 2.x only
def foo(a, (b, c), d):
pass
foo(<arg1>1, <arg23>range(2), <arg4>4)
@@ -0,0 +1,6 @@
class A:
def foo(self, a):
pass
a = A()
A.foo(<arg1>a, <arg2>1)
@@ -0,0 +1,4 @@
def foo(a, b, c):
pass
foo(<arg1>1, <arg2>2, <arg3>3)
@@ -0,0 +1,6 @@
class A:
def foo(self, a):
pass
a = A()
a.foo(<arg1>1)
+4
View File
@@ -0,0 +1,4 @@
def foo(a, b, c):
pass
foo(<arg1>1, <arg2>*(2, <arg2a>3))
@@ -0,0 +1,4 @@
def foo(a, b, *c, **d):
pass
foo(<arg1>1, <arg2>2, <arg3>3, <arg4>4, <arg5>x=1, <arg6>y=2)
@@ -0,0 +1,4 @@
def foo(a, b, *c):
pass
foo(<arg1>1, <arg2>2, <arg3>3, <arg4>4)
@@ -23,7 +23,7 @@ PyFile:ConditionalParenLambda.py
PsiElement(Py:LAMBDA_KEYWORD)('lambda')
PsiWhiteSpace(' ')
PyParameterList
PyParameter
PyNamedParameter('x')
PsiElement(Py:IDENTIFIER)('x')
PsiElement(Py:COLON)(':')
PsiWhiteSpace(' ')
+1 -1
View File
@@ -1,5 +1,5 @@
PyFile:DecoratedFunction.py
PyFunction
PyFunction('foo')
PyDecoratorList
PyDecorator: @staticmethod
PsiElement(Py:AT)('@')
+1 -1
View File
@@ -4,7 +4,7 @@ PyFile:LambdaConditional.py
PsiElement(Py:LAMBDA_KEYWORD)('lambda')
PsiWhiteSpace(' ')
PyParameterList
PyParameter
PyNamedParameter('x')
PsiElement(Py:IDENTIFIER)('x')
PsiElement(Py:COLON)(':')
PsiWhiteSpace(' ')
+1 -1
View File
@@ -1,5 +1,5 @@
PyFile:YieldInAssignment.py
PyFunction
PyFunction('f')
PsiElement(Py:DEF_KEYWORD)('def')
PsiWhiteSpace(' ')
PsiElement(Py:IDENTIFIER)('f')
+1 -1
View File
@@ -1,5 +1,5 @@
PyFile:YieldInAugAssignment.py
PyFunction
PyFunction('g')
PsiElement(Py:DEF_KEYWORD)('def')
PsiWhiteSpace(' ')
PsiElement(Py:IDENTIFIER)('g')
+1 -1
View File
@@ -1,5 +1,5 @@
PyFile:YieldInParentheses.py
PyFunction
PyFunction('f')
PsiElement(Py:DEF_KEYWORD)('def')
PsiWhiteSpace(' ')
PsiElement(Py:IDENTIFIER)('f')
+1 -1
View File
@@ -1,5 +1,5 @@
PyFile:YieldStatement.py
PyFunction
PyFunction('f')
PsiElement(Py:DEF_KEYWORD)('def')
PsiWhiteSpace(' ')
PsiElement(Py:IDENTIFIER)('f')
@@ -0,0 +1,398 @@
package com.jetbrains.python;
import com.intellij.lang.parameterInfo.CreateParameterInfoContext;
import com.intellij.lang.parameterInfo.ParameterInfoHandler;
import com.intellij.lang.parameterInfo.ParameterInfoUIContextEx;
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.psi.PyArgumentList;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
/**
* Tests parameter info available via ^P at call sites.
* <br/>User: dcheryasov
* Date: Jul 14, 2009 3:42:44 AM
*/
public class PyParameterInfoTest extends MarkedTestCase {
protected String getTestDataPath() {
return PathManager.getHomePath() + "/plugins/python/testData/paramInfo/";
}
public void testSimpleFunction() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
PsiElement arg1 = marks.get("<arg1>");
feignCtrlP(arg1.getTextOffset()).check("a,b,c", new String[]{"a,"});
feignCtrlP(arg1.getTextOffset()+1).check("a,b,c", new String[]{"a,"});
feignCtrlP(arg1.getTextOffset()-3).assertNotFound(); // ^P before arglist gives nothing
PsiElement arg2 = marks.get("<arg2>");
feignCtrlP(arg2.getTextOffset()).check("a,b,c", new String[]{"b,"});
feignCtrlP(arg2.getTextOffset()+1).check("a,b,c", new String[]{"b,"});
feignCtrlP(arg2.getTextOffset()+2).check("a,b,c", new String[]{"c"}); // one too far after arg2, and we came to arg3
PsiElement arg3 = marks.get("<arg3>");
feignCtrlP(arg3.getTextOffset()).check("a,b,c", new String[]{"c"});
feignCtrlP(arg3.getTextOffset()+1).check("a,b,c", new String[]{"c"});
feignCtrlP(arg3.getTextOffset()-1).check("a,b,c", new String[]{"c"}); // space before arg goes to that arg
feignCtrlP(arg3.getTextOffset()+2).assertNotFound(); // ^P on a ")" gives nothing
}
public void testStarredFunction() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
PsiElement arg1 = marks.get("<arg1>");
feignCtrlP(arg1.getTextOffset()).check("a,b,*c", new String[]{"a,"});
feignCtrlP(arg1.getTextOffset()+1).check("a,b,*c", new String[]{"a,"});
PsiElement arg2 = marks.get("<arg2>");
feignCtrlP(arg2.getTextOffset()).check("a,b,*c", new String[]{"b,"});
feignCtrlP(arg2.getTextOffset()+1).check("a,b,*c", new String[]{"b,"});
PsiElement arg3 = marks.get("<arg3>");
feignCtrlP(arg3.getTextOffset()).check("a,b,*c", new String[]{"*c"});
feignCtrlP(arg3.getTextOffset()+1).check("a,b,*c", new String[]{"*c"});
PsiElement arg4 = marks.get("<arg4>");
feignCtrlP(arg4.getTextOffset()).check("a,b,*c", new String[]{"*c"});
feignCtrlP(arg4.getTextOffset()+1).check("a,b,*c", new String[]{"*c"});
feignCtrlP(arg4.getTextOffset()+2).assertNotFound();
}
public void testKwdFunction() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
PsiElement arg1 = marks.get("<arg1>");
feignCtrlP(arg1.getTextOffset()).check("a,b,**c", new String[]{"a,"});
feignCtrlP(arg1.getTextOffset()+1).check("a,b,**c", new String[]{"a,"});
PsiElement arg2 = marks.get("<arg2>");
feignCtrlP(arg2.getTextOffset()).check("a,b,**c", new String[]{"b,"});
feignCtrlP(arg2.getTextOffset()+1).check("a,b,**c", new String[]{"b,"});
PsiElement arg3 = marks.get("<arg3>");
feignCtrlP(arg3.getTextOffset()).check("a,b,**c", new String[]{"**c"});
feignCtrlP(arg3.getTextOffset()+1).check("a,b,**c", new String[]{"**c"});
PsiElement arg4 = marks.get("<arg4>");
feignCtrlP(arg4.getTextOffset()).check("a,b,**c", new String[]{"**c"});
feignCtrlP(arg4.getTextOffset()+1).check("a,b,**c", new String[]{"**c"});
}
public void testKwdOutOfOrder() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,**c", new String[]{"**c"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,b,**c", new String[]{"b,"});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("a,b,**c", new String[]{"a,"});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,**c", new String[]{"**c"});
}
public void testStarArg() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,c", new String[]{"a,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,b,c", new String[]{"b,","c"});
feignCtrlP(marks.get("<arg2a>").getTextOffset()).check("a,b,c", new String[]{"b,","c"});
}
public void testKwdArg() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,c", new String[]{"a,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,b,c", new String[]{"b,","c"});
feignCtrlP(marks.get("<arg2a>").getTextOffset()).check("a,b,c", new String[]{"b,","c"});
}
public void testKwdArgOutOfOrder() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,c", new String[]{"b,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,b,c", new String[]{"a,","c"});
feignCtrlP(marks.get("<arg2a>").getTextOffset()).check("a,b,c", new String[]{"a,","c"});
}
public void testStarredAndKwdFunction() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 6);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,*c,**d", new String[]{"a,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,b,*c,**d", new String[]{"b,"});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("a,b,*c,**d", new String[]{"*c,"});
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("a,b,*c,**d", new String[]{"*c,"});
feignCtrlP(marks.get("<arg5>").getTextOffset()).check("a,b,*c,**d", new String[]{"**d"});
feignCtrlP(marks.get("<arg6>").getTextOffset()).check("a,b,*c,**d", new String[]{"**d"});
}
public void testNestedArg() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,(b,c),d", new String[]{"a,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,(b,c),d", new String[]{"b,"});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("a,(b,c),d", new String[]{"c"});
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("a,(b,c),d", new String[]{"d"});
feignCtrlP(marks.get("<arg2>").getTextOffset()-2).check("a,(b,c),d", new String[]{}); // before nested tuple: no arg matches
}
public void testDoubleNestedArg() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 5);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,(b,(c,d)),e", new String[]{"a,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,(b,(c,d)),e", new String[]{"b,"});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("a,(b,(c,d)),e", new String[]{"c,"});
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("a,(b,(c,d)),e", new String[]{"d"});
feignCtrlP(marks.get("<arg5>").getTextOffset()).check("a,(b,(c,d)),e", new String[]{"e"});
}
public void testNestedMultiArg() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,(b,c),d", new String[]{"a,"});
feignCtrlP(marks.get("<arg23>").getTextOffset()).check("a,(b,c),d", new String[]{"b,","c"});
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("a,(b,c),d", new String[]{"d"});
}
public void testStarredParam() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,c", new String[]{"a,"});
feignCtrlP(marks.get("<arg23>").getTextOffset()).check("a,b,c", new String[]{"b,","c"});
}
public void testStarredParamAndArg() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a,b,*c", new String[]{"a,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a,b,*c", new String[]{"b,"});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("a,b,*c", new String[]{"*c"});
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("a,b,*c", new String[]{"*c"});
}
public void testSimpleMethod() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self,a", new String[]{"a"}, new String[]{"self,"});
}
public void testSimpleClassFunction() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self,a", new String[]{"self,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self,a", new String[]{"a"});
}
// TODO: add method tests with decorators when a mock SDK is available
/**
* Imitates pressing of Ctrl+P; fails if results are not as expected.
* @param offset offset of 'cursor' where ^P is pressed.
* @return a {@link Collector} with collected hint info.
* @throws Exception if it fails
*/
private Collector feignCtrlP(int offset) throws Exception {
Collector collector = new Collector(getProject(), getFile(), offset);
PyParameterInfoHandler handler = new PyParameterInfoHandler();
collector.setParameterOwner(handler.findElementForParameterInfo(collector)); // finds arglist, sets items to show
if (collector.getParameterOwner() != null) {
assertEquals("Collected one analysis result", 1, collector.myItems.length);
handler.updateParameterInfo((PyArgumentList)collector.getParameterOwner(), collector); // moves offset to correct parameter
handler.updateUI((PyArgumentList.AnalysisResult)collector.getItemsToShow()[0], collector); // sets hint text and flags
}
return collector;
}
/**
* Imitates the normal UI contexts to the extent we use it. Collects highlighting.
*/
private static class Collector implements ParameterInfoUIContextEx, CreateParameterInfoContext, UpdateParameterInfoContext {
private PsiFile myFile;
private int myOffset;
private int myIndex;
private Object[] myItems;
private Project myProject;
private Editor myEditor;
private PyArgumentList myParamOwner;
private String[] myTexts;
private EnumSet<Flag>[] myFlags;
private Collector(Project project, PsiFile file, int offset) {
myProject = project;
myEditor = null;
myFile = file;
myOffset = offset;
}
public void setupUIComponentPresentation(String[] texts, EnumSet<Flag>[] flags, Color background) {
assert texts.length == flags.length;
myTexts = texts;
myFlags = flags;
}
public void setupUIComponentPresentation(String text, int highlightStartOffset, int highlightEndOffset, boolean isDisabled,
boolean strikeout, boolean isDisabledBeforeHighlight, Color background) {
// nothing, we don't use it
}
public boolean isUIComponentEnabled() {
return true;
}
public boolean isUIComponentEnabled(int index) {
return true;
}
public void setUIComponentEnabled(boolean enabled) { }
public void setUIComponentEnabled(int index, boolean b) { }
public int getCurrentParameterIndex() {
return myIndex;
}
public void removeHint() { }
public void setParameterOwner(PsiElement o) {
assertTrue("Found element is a python arglist", o == null || o instanceof PyArgumentList);
myParamOwner = (PyArgumentList)o;
}
public PsiElement getParameterOwner() {
return myParamOwner;
}
public void setHighlightedParameter(Object parameter) {
// nothing, we don't use it
}
public void setCurrentParameter(int index) {
myIndex = index;
}
public Color getDefaultParameterColor() {
return java.awt.Color.BLACK;
}
public Object[] getItemsToShow() {
return myItems;
}
public void setItemsToShow(Object[] items) {
myItems = items;
}
public void showHint(PsiElement element, int offset, ParameterInfoHandler handler) { }
public int getParameterListStart() {
return 0; // we don't use it
}
public Object[] getObjectsToView() {
return null; // we don't use it
}
public PsiElement getHighlightedElement() {
return null; // we don't use it
}
public void setHighlightedElement(PsiElement elements) {
// nothing, we don't use it
}
public Project getProject() {
return myProject;
}
public PsiFile getFile() {
return myFile;
}
public int getOffset() {
return myOffset;
}
@NotNull
public Editor getEditor() {
return myEditor;
}
/**
* Checks if hint data look as expected.
* @param text expected text of the hint, without formatting
* @param highlighted expected highlighted substrings of hint
* @param disabled expected disabled substrings of hint
*/
public void check(String text, String[] highlighted, String[] disabled) {
assertEquals("Signature", text, PyUtil.joinSubarray(myTexts, 0, 1000, "", new StringBuilder()).toString());
StringBuilder wrongs = new StringBuilder();
// see if highlighted matches
Set<String> highlight_set = new HashSet<String>();
highlight_set.addAll(Arrays.asList(highlighted));
for (int i=0; i < myTexts.length; i += 1) {
if (myFlags[i].contains(Flag.HIGHLIGHT) && !highlight_set.contains(myTexts[i])) {
wrongs.append("Highlighted unexpected '").append(myTexts[i]).append("'. ");
}
}
for (int i=0; i < myTexts.length; i += 1) {
if (!myFlags[i].contains(Flag.HIGHLIGHT) && highlight_set.contains(myTexts[i])) {
wrongs.append("Not highlighted expected '").append(myTexts[i]).append("'. ");
}
}
// see if disabled matches
Set<String> disabled_set = new HashSet<String>();
disabled_set.addAll(Arrays.asList(disabled));
for (int i=0; i < myTexts.length; i += 1) {
if (myFlags[i].contains(Flag.DISABLE) && !disabled_set.contains(myTexts[i])) {
wrongs.append("Highlighted unexpected '").append(myTexts[i]).append("'. ");
}
}
for (int i=0; i < myTexts.length; i += 1) {
if (!myFlags[i].contains(Flag.DISABLE) && disabled_set.contains(myTexts[i])) {
wrongs.append("Not disabled expected '").append(myTexts[i]).append("'. ");
}
}
//
if (wrongs.length() > 0) fail(wrongs.toString());
}
public void check(String text, String[] highlighted) {
check(text, highlighted, new String[0]);
}
public void assertNotFound() {
assertNull(myParamOwner);
}
}
}
@@ -136,7 +136,7 @@ public class PyResolveTest extends ResolveTestCase {
public void testLambda() throws Exception {
PsiElement targetElement = resolve();
assertTrue(targetElement instanceof PyParameter);
assertTrue(targetElement instanceof PyNamedParameter);
}
public void testSuperField() throws Exception {