diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 73f6d6f394cc..6c1bea107187 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -389,6 +389,7 @@ + diff --git a/python/src/com/jetbrains/python/PyParameterInfoHandler.java b/python/src/com/jetbrains/python/PyParameterInfoHandler.java index 07b004da2c68..d1a5bd5afb5c 100644 --- a/python/src/com/jetbrains/python/PyParameterInfoHandler.java +++ b/python/src/com/jetbrains/python/PyParameterInfoHandler.java @@ -39,7 +39,9 @@ public class PyParameterInfoHandler implements ParameterInfoHandler types = node.getSuperClassTypes(myTypeEvalContext); + for (PyClassLikeType type : types) { + if (type == null) return; + final String qName = type.getClassQName(); + if (qName != null && qName.contains(PyNames.TEST_CASE)) return; + if (!(type instanceof PyClassType)) return; } + final PyFunction init = node.findInitOrNew(true); if (init == null) { registerProblem(node.getNameIdentifier(), PyBundle.message("INSP.class.has.no.init"), diff --git a/python/src/com/jetbrains/python/inspections/PyOldStyleClassesInspection.java b/python/src/com/jetbrains/python/inspections/PyOldStyleClassesInspection.java index c8ddf0034715..5aa05194ea4b 100644 --- a/python/src/com/jetbrains/python/inspections/PyOldStyleClassesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyOldStyleClassesInspection.java @@ -62,6 +62,7 @@ public class PyOldStyleClassesInspection extends PyInspection { if (klass != null && !klass.isNewStyleClass()) { final List types = klass.getSuperClassTypes(myTypeEvalContext); for (PyClassLikeType type : types) { + if (type == null) return; final String qName = type.getClassQName(); if (qName != null && qName.contains("PyQt")) return; if (!(type instanceof PyClassType)) return; diff --git a/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java b/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java index 7122f4c93bc2..4f71bd786f70 100644 --- a/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java @@ -79,12 +79,12 @@ public class CallArgumentsMappingImpl implements CallArgumentsMapping { PyNamedParameter kwd_par = null; // **param PyNamedParameter tuple_par = null; // *param Set mapped_args = new HashSet(); - final int implicit_offset = resolved_callee.getImplicitOffset(); + final int implicitOffset = resolved_callee.getImplicitOffset(); int positional_index = 0; // up to this index parameters are positional // check positional arguments, fill slots int i = 0; for (PyParameter par : parameters) { - if (tuple_par == null && kwd_par == null && positional_index < implicit_offset) { + if (tuple_par == null && kwd_par == null && positional_index < implicitOffset) { positional_index += 1; continue; } @@ -107,14 +107,14 @@ public class CallArgumentsMappingImpl implements CallArgumentsMapping { i += 1; } // rule out 'self' or other implicit params - for (i=0; i < implicit_offset && i < parameters.size(); i+=1) { + for (i=0; i < implicitOffset && i < parameters.size(); i+=1) { slots.remove(parameters.get(i).getAsNamed()); positional_index += 1; } // now params to the left of positional_index are positional. // map positional args to positional params. // we assume that implicitly skipped parameters are never nested tuples. no idea when they could ever be. - int cnt = implicit_offset; + int cnt = implicitOffset; int positional_bound = arguments.length; // to the right of this pos args are verboten ListIterator unmatched_arg_iter = unmatched_args.listIterator(); while (unmatched_arg_iter.hasNext()) { @@ -290,10 +290,12 @@ public class CallArgumentsMappingImpl implements CallArgumentsMapping { } // map unmapped named params to **kwarg if (myKwdArg != null) { - for (PyParameter par : parameters) { - PyNamedParameter n_par = par.getAsNamed(); - if (n_par != null && !n_par.isKeywordContainer() && !n_par.isPositionalContainer() && slots.get(n_par) == null) { - slots.put(n_par, myKwdArg); + for (int j = implicitOffset; j != parameters.size(); ++j) { + final PyParameter par = parameters.get(j); + final PyNamedParameter namedParameter = par.getAsNamed(); + if (namedParameter != null && !namedParameter.isKeywordContainer() + && !namedParameter.isPositionalContainer() && slots.get(namedParameter) == null) { + slots.put(namedParameter, myKwdArg); } } } diff --git a/python/testData/paramInfo/KwdArgInClass.py b/python/testData/paramInfo/KwdArgInClass.py new file mode 100644 index 000000000000..8bfc48529a42 --- /dev/null +++ b/python/testData/paramInfo/KwdArgInClass.py @@ -0,0 +1,9 @@ +class A: + def foo(self, e): + self.kw = {} + self.add_comm(**self.kw) + + + def add_comm(self, **kw): + self.kw = [] + diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index d4f40cf21e94..1c931314f20c 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -132,6 +132,11 @@ public class PyParameterInfoTest extends LightMarkedTestCase { feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"b, ","c"}); } + public void testKwdArgInClass() { + Map marks = loadTest(1); + feignCtrlP(marks.get("").getTextOffset()).check("self, **kw", new String[]{"**kw"}, new String[]{"self, "}); + } + public void testKwdArgOutOfOrder() { Map marks = loadTest(3);