Merge branch 'vlan/pyi'
authorAndrey Vlasovskikh <andrey.vlasovskikh@jetbrains.com>
Wed, 9 Sep 2015 08:46:04 +0000 (11:46 +0300)
committerAndrey Vlasovskikh <andrey.vlasovskikh@jetbrains.com>
Wed, 9 Sep 2015 08:46:04 +0000 (11:46 +0300)
89 files changed:
python/psi-api/src/com/jetbrains/python/psi/CallArgumentsMapping.java [deleted file]
python/psi-api/src/com/jetbrains/python/psi/PyArgumentList.java
python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java
python/src/META-INF/python-core.xml
python/src/com/jetbrains/numpy/codeInsight/NumpyDocStringTypeProvider.java
python/src/com/jetbrains/numpy/documentation/NumPyDocString.java
python/src/com/jetbrains/python/PyParameterInfoHandler.java
python/src/com/jetbrains/python/codeInsight/completion/PyFunctionInsertHandler.java
python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java
python/src/com/jetbrains/python/findUsages/PyUsageTypeProvider.java
python/src/com/jetbrains/python/inspections/PyArgumentEqualDefaultInspection.java
python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java
python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java
python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java
python/src/com/jetbrains/python/inspections/PyPropertyDefinitionInspection.java
python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java
python/src/com/jetbrains/python/inspections/quickfix/PyRemoveParameterQuickFix.java
python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java
python/src/com/jetbrains/python/psi/PyUtil.java
python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java [deleted file]
python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java
python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java
python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java
python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java
python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java
python/src/com/jetbrains/python/psi/types/PyTypeChecker.java
python/src/com/jetbrains/python/pyi/PyiClassMembersProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiFile.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiFileElementType.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiFileType.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiFileTypeFactory.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiLanguageDialect.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiModuleMembersProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiParserDefinition.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiRelatedItemLineMarkerProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiTypeProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiUtil.java [new file with mode: 0644]
python/src/com/jetbrains/python/pyi/PyiVisitorFilter.java [new file with mode: 0644]
python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java
python/testData/inspections/PyArgumentListInspection/py1268.py
python/testData/inspections/PyArgumentListInspection/py3k.py
python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py
python/testData/inspections/PyTypeCheckerInspection/ClassNew.py [new file with mode: 0644]
python/testData/inspections/PyTypeCheckerInspection/ListTuple.py
python/testData/pyi/inspections/overloads/Overloads.py [new file with mode: 0644]
python/testData/pyi/inspections/overloads/m1.py [new file with mode: 0644]
python/testData/pyi/inspections/overloads/m1.pyi [new file with mode: 0644]
python/testData/pyi/inspections/pyiStatementEffect/PyiStatementEffect.pyi [new file with mode: 0644]
python/testData/pyi/inspections/pyiUnusedParameters/PyiUnusedParameters.pyi [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedClassAttributes/UnresolvedClassAttributes.py [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedClassAttributes/m1.py [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedClassAttributes/m1.pyi [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedModuleAttributes/UnresolvedModuleAttributes.py [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedModuleAttributes/m1.py [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedModuleAttributes/m1.pyi [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedModuleAttributes/m2.py [new file with mode: 0644]
python/testData/pyi/inspections/unresolvedModuleAttributes/m3.py [new file with mode: 0644]
python/testData/pyi/parsing/Simple.pyi [new file with mode: 0644]
python/testData/pyi/parsing/Simple.txt [new file with mode: 0644]
python/testData/pyi/pyiStubs/module_with_stub_in_path.pyi [new file with mode: 0644]
python/testData/pyi/resolve/builtinInt/BuiltinInt.pyi [new file with mode: 0644]
python/testData/pyi/resolve/classInsidePyiFile/ClassInsidePyiFile.pyi [new file with mode: 0644]
python/testData/pyi/resolve/fromPyiToClassInPy/FromPyiToClassInPy.pyi [new file with mode: 0644]
python/testData/pyi/resolve/fromPyiToClassInPy/m1.py [new file with mode: 0644]
python/testData/pyi/resolve/moduleAttribute/ModuleAttribute.py [new file with mode: 0644]
python/testData/pyi/resolve/moduleAttribute/m1.py [new file with mode: 0644]
python/testData/pyi/resolve/moduleAttribute/m1.pyi [new file with mode: 0644]
python/testData/pyi/type/functionParameter/FunctionParameter.py [new file with mode: 0644]
python/testData/pyi/type/functionParameter/FunctionParameter.pyi [new file with mode: 0644]
python/testData/pyi/type/functionReturnType/FunctionReturnType.py [new file with mode: 0644]
python/testData/pyi/type/functionReturnType/FunctionReturnType.pyi [new file with mode: 0644]
python/testData/pyi/type/functionType/FunctionType.py [new file with mode: 0644]
python/testData/pyi/type/functionType/FunctionType.pyi [new file with mode: 0644]
python/testData/pyi/type/moduleAttribute/ModuleAttribute.py [new file with mode: 0644]
python/testData/pyi/type/moduleAttribute/ModuleAttribute.pyi [new file with mode: 0644]
python/testData/pyi/type/overloadedReturnType/OverloadedReturnType.py [new file with mode: 0644]
python/testData/pyi/type/overloadedReturnType/OverloadedReturnType.pyi [new file with mode: 0644]
python/testData/pyi/type/pyiOnPythonPath/PyiOnPythonPath.py [new file with mode: 0644]
python/testData/pyi/type/pyiOnPythonPath/module_with_stub_in_path.py [new file with mode: 0644]
python/testData/quickdoc/NumPyOnesDoc.html
python/testSrc/com/jetbrains/python/PyParameterInfoTest.java
python/testSrc/com/jetbrains/python/fixtures/PyMultiFileResolveTestCase.java
python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java
python/testSrc/com/jetbrains/python/pyi/PyiInspectionsTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/python/pyi/PyiParsingTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/python/pyi/PyiResolveTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/python/pyi/PyiTypeTest.java [new file with mode: 0644]

diff --git a/python/psi-api/src/com/jetbrains/python/psi/CallArgumentsMapping.java b/python/psi-api/src/com/jetbrains/python/psi/CallArgumentsMapping.java
deleted file mode 100644 (file)
index f8738c6..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * 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 org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Result of analysis of argument list application to the callee.
- * Contains neatly arranged lists and mappings between arguments and parameters,
- * including error diagnostics.
- */
-public interface CallArgumentsMapping {
-  /**
-   * @return A mapping argument->parameter for non-starred parameters (but includes starred argument).
-   */
-  @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 CallArgumentsMapping#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.
-   */
-  @Nullable
-  PyStarArgument getTupleArg();
-
-  /**
-   * @return A list of parameters mapped to a *arg.
-   */
-  @NotNull List<PyNamedParameter> getTupleMappedParams();
-
-  /**
-   * @return First **arg, or null.
-   */
-  @Nullable
-  PyStarArgument getKwdArg();
-
-  /**
-   * @return A list of parameters mapped to an **arg.
-   */
-  @NotNull List<PyNamedParameter> getKwdMappedParams();
-
-  /**
-   * @return A list of parameters for which no arguments were found ('missing').
-   */
-  @NotNull
-  List<PyNamedParameter> getUnmappedParams();
-
-
-  /**
-   * @return Lists all args with their flags.
-   * @see com.jetbrains.python.psi.CallArgumentsMapping.ArgFlag
-   */
-  Map<PyExpression, EnumSet<ArgFlag>> getArgumentFlags();
-
-  boolean hasProblems();
-  
-  /**
-   * @return result of a resolveCallee() against the function call to which the parameter list belongs.
-   */
-  @Nullable
-  PyCallExpression.PyMarkedCallee getMarkedCallee();
-
-  PyArgumentList getArgumentList();
-
-  /**
-   * Flags to mark analysis results for an argument.
-   * Theoretically can be used together, but currently only make sense as a single value per argument.
-   */
-  enum ArgFlag {
-    /** duplicate plain */          IS_DUP,
-    /** unexpected */               IS_UNMAPPED,
-    /** duplicate **arg */          IS_DUP_KWD,
-    /** duplicate *arg */           IS_DUP_TUPLE,
-    /** positional past keyword */  IS_POS_PAST_KWD,
-    /** *param is too long */       IS_TOO_LONG,
-  }
-}
index 31bb9d5bb8e32fdd5005b2c65edabd25fc801860..698e43763646f71e0e9d31e2bfee97a29074e3ab 100644 (file)
@@ -17,7 +17,6 @@ package com.jetbrains.python.psi;
 
 import com.intellij.lang.ASTNode;
 import com.jetbrains.python.FunctionParameter;
-import com.jetbrains.python.psi.resolve.PyResolveContext;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -63,19 +62,6 @@ public interface PyArgumentList extends PyElement {
   @Nullable
   PyCallExpression getCallExpression();
 
-  /**
-   * Tries to map the argument list to callee's idea of parameters.
-   *
-   * @param resolveContext the reference resolution context
-   * @param implicitOffset known from the context implicit offset
-   * @return a result object with mappings and diagnostic flags.
-   */
-  @NotNull
-  CallArgumentsMapping analyzeCall(PyResolveContext resolveContext, int implicitOffset);
-
-  @NotNull
-  CallArgumentsMapping analyzeCall(PyResolveContext resolveContext);
-
 
   @Nullable
   ASTNode getClosingParen();
index a2e0a1cb6a42d267786d0fce8001bd9d2d3fa12a..da96e7e29b610003e6f370ddb37284bc61d7f1ed 100644 (file)
@@ -22,6 +22,9 @@ import com.jetbrains.python.psi.resolve.PyResolveContext;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * Represents an entire call expression, like <tt>foo()</tt> or <tt>foo.bar[1]('x')</tt>.
  */
@@ -81,7 +84,7 @@ public interface PyCallExpression extends PyCallSiteExpression {
   PyExpression getKeywordArgument(String keyword);
 
   /**
-   * TODO: Copy/Paste with {@link com.jetbrains.python.psi.PyArgumentList#addArgument(PyExpression)}
+   * TODO: Copy/Paste with {@link PyArgumentList#addArgument(PyExpression)}
    * @param expression
    */
   void addArgument(PyExpression expression);
@@ -114,6 +117,12 @@ public interface PyCallExpression extends PyCallSiteExpression {
   @Nullable
   PyMarkedCallee resolveCallee(PyResolveContext resolveContext, int implicitOffset);
 
+  @NotNull
+  PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext);
+
+  @NotNull
+  PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext, int implicitOffset);
+
   /**
    * Checks if the unqualified name of the callee matches any of the specified names
    *
@@ -122,7 +131,6 @@ public interface PyCallExpression extends PyCallSiteExpression {
    */
   boolean isCalleeText(@NotNull String... nameCandidates);
 
-
   /**
    * Checks if the qualified name of the callee matches any of the specified names provided by provider.
    * @see com.jetbrains.python.nameResolver
@@ -131,6 +139,75 @@ public interface PyCallExpression extends PyCallSiteExpression {
    */
   boolean isCallee(@NotNull FQNamesProvider... name);
 
+  class PyArgumentsMapping {
+    @NotNull private final PyCallExpression myCallExpression;
+    @Nullable private final PyMarkedCallee myCallee;
+    @NotNull private final Map<PyExpression, PyNamedParameter> myMappedParameters;
+    @NotNull private final List<PyParameter> myUnmappedParameters;
+    @NotNull private final List<PyExpression> myUnmappedArguments;
+    @NotNull private final List<PyNamedParameter> myParametersMappedToVariadicPositionalArguments;
+    @NotNull private final List<PyNamedParameter> myParametersMappedToVariadicKeywordArguments;
+    @NotNull private final Map<PyExpression, PyTupleParameter> myMappedTupleParameters;
+
+    public PyArgumentsMapping(@NotNull PyCallExpression expression,
+                              @Nullable PyMarkedCallee markedCallee,
+                              @NotNull Map<PyExpression, PyNamedParameter> mappedParameters,
+                              @NotNull List<PyParameter> unmappedParameters,
+                              @NotNull List<PyExpression> unmappedArguments,
+                              @NotNull List<PyNamedParameter> parametersMappedToVariadicPositionalArguments,
+                              @NotNull List<PyNamedParameter> parametersMappedToVariadicKeywordArguments,
+                              @NotNull Map<PyExpression, PyTupleParameter> tupleMappedParameters) {
+      myCallExpression = expression;
+      myCallee = markedCallee;
+      myMappedParameters = mappedParameters;
+      myUnmappedParameters = unmappedParameters;
+      myUnmappedArguments = unmappedArguments;
+      myParametersMappedToVariadicPositionalArguments = parametersMappedToVariadicPositionalArguments;
+      myParametersMappedToVariadicKeywordArguments = parametersMappedToVariadicKeywordArguments;
+      myMappedTupleParameters = tupleMappedParameters;
+    }
+
+    @NotNull
+    public PyCallExpression getCallExpression() {
+      return myCallExpression;
+    }
+
+    @Nullable
+    public PyMarkedCallee getMarkedCallee() {
+      return myCallee;
+    }
+
+    @NotNull
+    public Map<PyExpression, PyNamedParameter> getMappedParameters() {
+      return myMappedParameters;
+    }
+
+    @NotNull
+    public List<PyParameter> getUnmappedParameters() {
+      return myUnmappedParameters;
+    }
+
+    @NotNull
+    public List<PyExpression> getUnmappedArguments() {
+      return myUnmappedArguments;
+    }
+
+    @NotNull
+    public List<PyNamedParameter> getParametersMappedToVariadicPositionalArguments() {
+      return myParametersMappedToVariadicPositionalArguments;
+    }
+
+    @NotNull
+    public List<PyNamedParameter> getParametersMappedToVariadicKeywordArguments() {
+      return myParametersMappedToVariadicKeywordArguments;
+    }
+
+    @NotNull
+    public Map<PyExpression, PyTupleParameter> getMappedTupleParameters() {
+      return myMappedTupleParameters;
+    }
+  }
+
   /**
    * Couples function with a flag describing the way it is called.
    */
index 29489c86a6184c49a6f845828b41c2b11f6e5a64..ef7f6fb1efc91681d126ce648e7d8269a0ed78c0 100644 (file)
 
     <!-- typing -->
     <multiHostInjector implementation="com.jetbrains.python.codeInsight.PyTypingAnnotationInjector"/>
+    <lang.parserDefinition language="PythonStub" implementationClass="com.jetbrains.python.pyi.PyiParserDefinition"/>
+    <fileTypeFactory implementation="com.jetbrains.python.pyi.PyiFileTypeFactory"/>
+    <codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.python.pyi.PyiRelatedItemLineMarkerProvider"/>
 
     <lang.inspectionSuppressor language="Python" implementationClass="com.jetbrains.python.inspections.PyInspectionsSuppressor"/>
     <refactoring.invertBoolean implementation="com.jetbrains.python.refactoring.invertBoolean.PyInvertBooleanDelegate"/>
 
     <!-- typing -->
     <typeProvider implementation="com.jetbrains.python.codeInsight.PyTypingTypeProvider"/>
+    <typeProvider implementation="com.jetbrains.python.pyi.PyiTypeProvider"/>
+    <pyModuleMembersProvider implementation="com.jetbrains.python.pyi.PyiModuleMembersProvider"/>
+    <pyClassMembersProvider implementation="com.jetbrains.python.pyi.PyiClassMembersProvider"/>
+    <visitorFilter language="PythonStub" implementationClass="com.jetbrains.python.pyi.PyiVisitorFilter"/>
 
     <typeProvider implementation="com.jetbrains.python.debugger.PyCallSignatureTypeProvider"/>
     <pyReferenceResolveProvider implementation="com.jetbrains.python.psi.resolve.PythonBuiltinReferenceResolveProvider"/>
index 0406f4694ba23012cb04242d28312a13b4023e35..e63abd21a5486abebab5f09629f442c81c8d1acc 100644 (file)
@@ -30,6 +30,7 @@ import com.jetbrains.python.documentation.PyDocumentationSettings;
 import com.jetbrains.python.psi.*;
 import com.jetbrains.python.psi.impl.PyBuiltinCache;
 import com.jetbrains.python.psi.impl.PyExpressionCodeFragmentImpl;
+import com.jetbrains.python.psi.types.PyNoneType;
 import com.jetbrains.python.psi.types.PyType;
 import com.jetbrains.python.psi.types.PyTypeProviderBase;
 import com.jetbrains.python.psi.types.TypeEvalContext;
@@ -244,8 +245,12 @@ public class NumpyDocStringTypeProvider extends PyTypeProviderBase {
 
   @Nullable
   private static PyType parseNumpyDocType(@NotNull PsiElement anchor, @NotNull String typeString) {
-    typeString = NumPyDocString.cleanupOptional(typeString);
+    final String withoutOptional = NumPyDocString.cleanupOptional(typeString);
     final Set<PyType> types = new LinkedHashSet<PyType>();
+    if (withoutOptional != null) {
+      typeString = withoutOptional;
+      types.add(PyNoneType.INSTANCE);
+    }
     for (String typeName : NumPyDocString.getNumpyUnionType(typeString)) {
       PyType parsedType = parseSingleNumpyDocType(anchor, typeName);
       if (parsedType != null) {
index f8563020dfc7620734aee8d2fce83b6367645a35..760369d407799f10c53a01fc276805905e96469a 100644 (file)
@@ -46,7 +46,6 @@ public class NumPyDocString {
   private static final Pattern REDIRECT = Pattern.compile("^Refer to `(.*)` for full documentation.$");
   private static final Pattern NUMPY_UNION_PATTERN = Pattern.compile("^\\{(.*)\\}$");
   private static final Pattern NUMPY_ARRAY_PATTERN = Pattern.compile("(\\(\\.\\.\\..*\\))(.*)");
-  private static final Pattern QUOTED_STRING_PATTERN = Pattern.compile("^(?:\\\"(.*)\\\")|(?:\\'(.*)\\')$");
 
   private final String mySignature;
   private final List<NumPyDocStringParameter> myParameters = new ArrayList<NumPyDocStringParameter>();
@@ -277,13 +276,13 @@ public class NumPyDocString {
     }
   }
 
-  @NotNull
+  @Nullable
   public static String cleanupOptional(@NotNull String typeString) {
     int index = typeString.indexOf(", optional");
     if (index >= 0) {
       return typeString.substring(0, index);
     }
-    return typeString;
+    return null;
   }
 
   @NotNull
@@ -299,23 +298,6 @@ public class NumPyDocString {
     return Arrays.asList(typeString.split(" *, *"));
   }
 
-  @NotNull
-  public static Set<String> extractPermissibleArgumentsFromNumpyDocType(String typeString) {
-    List<String> elements = getNumpyUnionType(cleanupOptional(typeString));
-    Set<String> result = new LinkedHashSet<String>();
-    for (String element : elements) {
-      Matcher matcher = QUOTED_STRING_PATTERN.matcher(element);
-      if (matcher.matches()) {
-        if (matcher.group(1) != null) {
-          result.add(matcher.group(1));
-        } else if (matcher.group(2) != null) {
-          result.add(matcher.group(2));
-        }
-      }
-    }
-    return result;
-  }
-
   public static class NotNumpyDocStringException extends Exception {
 
     public NotNumpyDocStringException(String signature) {
index 28ab2002f0270739a9c4b265a249d20a1dc0e628..10417ab59033159876e49e93b9e9deb3abe3e407 100644 (file)
@@ -26,6 +26,7 @@ import com.intellij.util.text.CharArrayUtil;
 import com.intellij.xml.util.XmlStringUtil;
 import com.jetbrains.python.psi.*;
 import com.jetbrains.python.psi.impl.ParamHelper;
+import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
 import com.jetbrains.python.psi.resolve.PyResolveContext;
 import com.jetbrains.python.psi.types.TypeEvalContext;
 import org.jetbrains.annotations.NotNull;
@@ -37,29 +38,37 @@ import static com.jetbrains.python.psi.PyCallExpression.PyMarkedCallee;
 /**
  * @author dcheryasov
  */
-public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentList, CallArgumentsMapping> {
+public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentList, PyCallExpression.PyArgumentsMapping> {
   private static  final String NO_PARAMS_MSG = CodeInsightBundle.message("parameter.info.no.parameters");
 
+  @Override
   public boolean couldShowInLookup() {
     return true;
   }
+
+  @Override
   public Object[] getParametersForLookup(final LookupElement item, final ParameterInfoContext context) {
-    return ArrayUtil.EMPTY_OBJECT_ARRAY;  // we don't
+    return ArrayUtil.EMPTY_OBJECT_ARRAY;
   }
 
-  public Object[] getParametersForDocumentation(final CallArgumentsMapping p, final ParameterInfoContext context) {
-    return ArrayUtil.EMPTY_OBJECT_ARRAY;  // we don't
+  @Override
+  public Object[] getParametersForDocumentation(final PyCallExpression.PyArgumentsMapping p, final ParameterInfoContext context) {
+    return ArrayUtil.EMPTY_OBJECT_ARRAY;
   }
 
+  @Override
   public PyArgumentList findElementForParameterInfo(@NotNull final CreateParameterInfoContext context) {
-    PyArgumentList arglist = findArgumentList(context);
-    if (arglist != null) {
-      final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(arglist.getProject(), arglist.getContainingFile());
-      final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext);
-      CallArgumentsMapping result = arglist.analyzeCall(resolveContext);
-      if (result.getMarkedCallee() != null) {
-        context.setItemsToShow(new Object[] { result });
-        return arglist;
+    PyArgumentList argumentList = findArgumentList(context);
+    if (argumentList != null) {
+      final PyCallExpression callExpr = argumentList.getCallExpression();
+      if (callExpr != null) {
+        final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(argumentList.getProject(), argumentList.getContainingFile());
+        final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext);
+        final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(resolveContext);
+        if (mapping.getMarkedCallee() != null) {
+          context.setItemsToShow(new Object[] { mapping });
+          return argumentList;
+        }
       }
     }
     return null;
@@ -69,10 +78,12 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
     return ParameterInfoUtils.findParentOfType(context.getFile(), context.getOffset(), PyArgumentList.class);
   }
 
+  @Override
   public void showParameterInfo(@NotNull final PyArgumentList element, @NotNull final CreateParameterInfoContext context) {
     context.showHint(element, element.getTextOffset(), this);
   }
 
+  @Override
   public PyArgumentList findElementForUpdatingParameterInfo(@NotNull final UpdateParameterInfoContext context) {
     return findArgumentList(context);
   }
@@ -82,17 +93,17 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
    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, @NotNull final UpdateParameterInfoContext context) {
-    if (context.getParameterOwner() != arglist) {
+  public void updateParameterInfo(@NotNull final PyArgumentList argumentList, @NotNull final UpdateParameterInfoContext context) {
+    if (context.getParameterOwner() != argumentList) {
       context.removeHint();
       return;
     }
     // align offset to nearest expression; context may point to a space, etc.
-    List<PyExpression> flat_args = PyUtil.flattenedParensAndLists(arglist.getArguments());
+    List<PyExpression> flat_args = PyUtil.flattenedParensAndLists(argumentList.getArguments());
     int alleged_cursor_offset = context.getOffset(); // this is already shifted backwards to skip spaces
 
-    final TextRange argListTextRange = arglist.getTextRange();
-    if (!argListTextRange.contains(alleged_cursor_offset) && arglist.getText().endsWith(")")) {
+    final TextRange argListTextRange = argumentList.getTextRange();
+    if (!argListTextRange.contains(alleged_cursor_offset) && argumentList.getText().endsWith(")")) {
       context.removeHint();
       return;
     }
@@ -125,15 +136,15 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
   }
 
   @Override
-  public void updateUI(final CallArgumentsMapping prevResult, @NotNull final ParameterInfoUIContext context) {
-    if (prevResult == null) return;
-    final PyArgumentList argList = prevResult.getArgumentList();
-    if (!argList.isValid()) return;
+  public void updateUI(final PyCallExpression.PyArgumentsMapping oldMapping, @NotNull final ParameterInfoUIContext context) {
+    if (oldMapping == null) return;
+    final PyCallExpression callExpression = oldMapping.getCallExpression();
+    if (!callExpression.isValid()) return;
     // really we need to redo analysis every UI update; findElementForParameterInfo isn't called while typing
-    final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(argList.getProject(), argList.getContainingFile());
+    final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(callExpression.getProject(), callExpression.getContainingFile());
     final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext);
-    final CallArgumentsMapping argumentsMapping = argList.analyzeCall(resolveContext);
-    final PyMarkedCallee marked = argumentsMapping.getMarkedCallee();
+    final PyCallExpression.PyArgumentsMapping mapping = callExpression.mapArguments(resolveContext);
+    final PyMarkedCallee marked = mapping.getMarkedCallee();
     if (marked == null) return; // resolution failed
     final PyCallable callable = marked.getCallable();
 
@@ -154,8 +165,8 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
       hintFlags.get(parameterToIndex.get(namedParameters.get(i))).add(ParameterInfoUIContextEx.Flag.DISABLE); // show but mark as absent
     }
 
-    final List<PyExpression> flattenedArgs = PyUtil.flattenedParensAndLists(argList.getArguments());
-    int lastParamIndex = collectHighlights(argumentsMapping, parameterList, parameterToIndex, hintFlags, flattenedArgs, currentParamOffset);
+    final List<PyExpression> flattenedArgs = PyUtil.flattenedParensAndLists(callExpression.getArguments());
+    int lastParamIndex = collectHighlights(mapping, parameterList, parameterToIndex, hintFlags, flattenedArgs, currentParamOffset);
 
     highlightNext(marked, parameterList, namedParameters, parameterToIndex, hintFlags, flattenedArgs.isEmpty(), lastParamIndex);
 
@@ -227,56 +238,69 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
    *
    * @return index of last parameter
    */
-  private static int collectHighlights(@NotNull final CallArgumentsMapping argumentsMapping,
+  private static int collectHighlights(@NotNull final PyCallExpression.PyArgumentsMapping mapping,
                                        @NotNull final List<PyParameter> parameterList,
                                        @NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
                                        @NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
                                        @NotNull final List<PyExpression> flatArgs, int currentParamOffset) {
-    final PyMarkedCallee callee = argumentsMapping.getMarkedCallee();
+    final PyMarkedCallee callee = mapping.getMarkedCallee();
     assert callee != null;
     int lastParamIndex = callee.getImplicitOffset();
+    final Map<PyExpression, PyNamedParameter> mappedParameters = mapping.getMappedParameters();
+    final Map<PyExpression, PyTupleParameter> mappedTupleParameters = mapping.getMappedTupleParameters();
     for (PyExpression arg : flatArgs) {
       final boolean mustHighlight = arg.getTextRange().contains(currentParamOffset);
       PsiElement seeker = arg;
-      while (!(seeker instanceof PyArgumentList) && seeker instanceof PyExpression && !argumentsMapping.getPlainMappedParams().containsKey(seeker)) {
-        seeker = seeker.getParent(); // flattener may have flattened a tuple arg that is mapped to a plain param; find it.
+      // An argument tuple may have been flattened; find it
+      while (!(seeker instanceof PyArgumentList) && seeker instanceof PyExpression && !mappedParameters.containsKey(seeker)) {
+        seeker = seeker.getParent();
       }
       if (seeker instanceof PyExpression) {
-        final PyNamedParameter parameter = argumentsMapping.getPlainMappedParams().get((PyExpression)seeker);
+        final PyNamedParameter parameter = mappedParameters.get((PyExpression)seeker);
         lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
         if (parameter != null) {
           highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
         }
       }
-      else if (arg == argumentsMapping.getTupleArg()) {
-        // mark all params that map to *arg
-        for (PyNamedParameter parameter : argumentsMapping.getTupleMappedParams()) {
+      else if (PyCallExpressionHelper.isVariadicPositionalArgument(arg)) {
+        for (PyNamedParameter parameter : mapping.getParametersMappedToVariadicPositionalArguments()) {
           lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
           highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
         }
       }
-      else if (arg == argumentsMapping.getKwdArg()) {
-        // mark all n_params that map to **arg
-        for (PyNamedParameter parameter : argumentsMapping.getKwdMappedParams()) {
+      else if (PyCallExpressionHelper.isVariadicKeywordArgument(arg)) {
+        for (PyNamedParameter parameter : mapping.getParametersMappedToVariadicKeywordArguments()) {
           lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
           highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
         }
       }
       else {
-        // maybe it's mapped to a nested tuple?
-        final List<PyNamedParameter> namedParameters = argumentsMapping.getNestedMappedParams().get(arg);
-        if (namedParameters != null) {
-          for (PyNamedParameter parameter : namedParameters) {
+        final PyTupleParameter tupleParameter = mappedTupleParameters.get(arg);
+        if (tupleParameter != null) {
+          for (PyNamedParameter parameter : getFlattenedTupleParameterComponents(tupleParameter)) {
             lastParamIndex = Math.max(lastParamIndex, parameterList.indexOf(parameter));
             highlightParameter(parameter, parameterToIndex, hintFlags, mustHighlight);
           }
         }
       }
-      // else: stay unhighlighted
     }
     return lastParamIndex;
   }
 
+  @NotNull
+  private static List<PyNamedParameter> getFlattenedTupleParameterComponents(@NotNull PyTupleParameter parameter) {
+    final List<PyNamedParameter> results = new ArrayList<PyNamedParameter>();
+    for (PyParameter component : parameter.getContents()) {
+      if (component instanceof PyNamedParameter) {
+        results.add((PyNamedParameter)component);
+      }
+      else if (component instanceof PyTupleParameter) {
+        results.addAll(getFlattenedTupleParameterComponents((PyTupleParameter)component));
+      }
+    }
+    return results;
+  }
+
   private static void highlightParameter(@NotNull final PyNamedParameter parameter,
                                         @NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
                                         @NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
index 5f8eb0acac2892e815a2f05cb4f3b2261f63e439..5007003eb138a7a91c2f5d082bc6836baf82b624 100644 (file)
@@ -24,6 +24,7 @@ import com.intellij.psi.util.PsiTreeUtil;
 import com.jetbrains.python.psi.PyFunction;
 import com.jetbrains.python.psi.PyReferenceExpression;
 import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
+import com.jetbrains.python.psi.resolve.PyResolveContext;
 
 /**
  * @author yole
@@ -52,7 +53,7 @@ public class PyFunctionInsertHandler extends ParenthesesInsertHandler<LookupElem
     final PsiElement element = context.getFile().findElementAt(context.getStartOffset());
     PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
     int implicitArgsCount = refExpr != null
-                            ? PyCallExpressionHelper.getImplicitArgumentCount(refExpr, function)
+                            ? PyCallExpressionHelper.getImplicitArgumentCount(refExpr, function, PyResolveContext.noImplicits())
                             : 0;
     return function.getParameterList().getParameters().length > implicitArgsCount;
   }
index 4359ada93bce2b39a31fd6dd99ee6737bbd1cc42..e9ed58c9425a0b5b380140b467afff0d2dbdb832 100644 (file)
@@ -139,10 +139,12 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
   public PyType getCallType(@NotNull PyFunction function, @Nullable PyCallSiteExpression callSite, @NotNull TypeEvalContext context) {
     final String qname = getQualifiedName(function, callSite);
     if (qname != null) {
-      if (OPEN_FUNCTIONS.contains(qname) && callSite != null) {
-        final PyTypeChecker.AnalyzeCallResults results = PyTypeChecker.analyzeCallSite(callSite, context);
-        if (results != null) {
-          final PyType type = getOpenFunctionType(qname, results.getArguments(), callSite);
+      if (OPEN_FUNCTIONS.contains(qname) && callSite instanceof PyCallExpression) {
+        final PyCallExpression callExpr = (PyCallExpression)callSite;
+        final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
+        final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments( resolveContext);
+        if (mapping.getMarkedCallee() != null) {
+          final PyType type = getOpenFunctionType(qname, mapping.getMappedParameters(), callSite);
           if (type != null) {
             return type;
           }
index b4590bd47461e11ff916a187be8b8425ad48296b..5152a0dd8a34c6832bac0e72d48395701c57f3f3 100644 (file)
@@ -17,18 +17,16 @@ package com.jetbrains.python.findUsages;
 
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.usages.PsiElementUsageTarget;
 import com.intellij.usages.UsageTarget;
 import com.intellij.usages.impl.rules.UsageType;
 import com.intellij.usages.impl.rules.UsageTypeProviderEx;
 import com.jetbrains.python.PyNames;
 import com.jetbrains.python.psi.*;
-import com.jetbrains.python.psi.impl.CallArgumentsMappingImpl;
 import com.jetbrains.python.psi.impl.PyPsiUtils;
+import com.jetbrains.python.psi.types.PyStructuralType;
 import com.jetbrains.python.psi.types.PyType;
 import com.jetbrains.python.psi.types.TypeEvalContext;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 /**
  * @author yole
@@ -38,7 +36,6 @@ public class PyUsageTypeProvider implements UsageTypeProviderEx {
   private static final UsageType UNTYPED = new UsageType("Untyped (probable) usage");
   private static final UsageType USAGE_IN_ISINSTANCE = new UsageType("Usage in isinstance()");
   private static final UsageType USAGE_IN_SUPERCLASS = new UsageType("Usage in superclass list");
-  private static final UsageType SIGNATURE_MISMATCH = new UsageType("Untyped (probable) usage, signature mismatch");
 
   @Override
   public UsageType getUsageType(PsiElement element) {
@@ -55,11 +52,7 @@ public class PyUsageTypeProvider implements UsageTypeProviderEx {
         if (qualifier != null) {
           final TypeEvalContext context = TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile());
           final PyType type = context.getType(qualifier);
-          if (type == null) {
-            final PyCallExpression call = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
-            if (call != null && element == call.getCallee()) {
-              return checkMatchingSignatureGroup(call, targets, context);
-            }
+          if (type == null || type instanceof PyStructuralType) {
             return UNTYPED;
           }
         }
@@ -87,22 +80,4 @@ public class PyUsageTypeProvider implements UsageTypeProviderEx {
     }
     return null;
   }
-
-  @Nullable
-  private static UsageType checkMatchingSignatureGroup(PyCallExpression call, UsageTarget[] targets, @NotNull TypeEvalContext context) {
-    if (targets.length == 1 && targets[0] instanceof PsiElementUsageTarget) {
-      final PsiElement element = ((PsiElementUsageTarget)targets[0]).getElement();
-      if (element instanceof PyFunction) {
-        PyFunction function = (PyFunction)element;
-        final PyFunction.Modifier modifier = function.getModifier();
-        PyCallExpression.PyMarkedCallee callee = new PyCallExpression.PyMarkedCallee(function, modifier, 1, true);
-        CallArgumentsMappingImpl mapping = new CallArgumentsMappingImpl(call.getArgumentList());
-        mapping.mapArguments(callee, context);
-        if (mapping.hasProblems()) {
-          return SIGNATURE_MISMATCH;
-        }
-      }
-    }
-    return null;
-  }
 }
index e2c51d1b5ab725e0684eed6883ef5b676c999c49..cd06a6d19e81f18abd9ecde5cbd8312a89cfdacf 100644 (file)
@@ -77,8 +77,7 @@ public class PyArgumentEqualDefaultInspection extends PyInspection {
       if (func != null && hasSpecialCasedDefaults(func, node)) {
         return;
       }
-      CallArgumentsMapping result = list.analyzeCall(getResolveContext());
-      checkArguments(result, node.getArguments());
+      checkArguments(node, node.getArguments());
     }
 
     private static boolean hasSpecialCasedDefaults(PyCallable callable, PsiElement anchor) {
@@ -97,10 +96,10 @@ public class PyArgumentEqualDefaultInspection extends PyInspection {
       return false;
     }
 
-    private void checkArguments(CallArgumentsMapping result, PyExpression[] arguments) {
-      Map<PyExpression, PyNamedParameter> mapping = result.getPlainMappedParams();
+    private void checkArguments(PyCallExpression callExpr, PyExpression[] arguments) {
+      final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(getResolveContext());
       Set<PyExpression> problemElements = new HashSet<PyExpression>();
-      for (Map.Entry<PyExpression, PyNamedParameter> e : mapping.entrySet()) {
+      for (Map.Entry<PyExpression, PyNamedParameter> e : mapping.getMappedParameters().entrySet()) {
         PyExpression defaultValue = e.getValue().getDefaultValue();
         if (defaultValue != null) {
           PyExpression key = e.getKey();
index 320ca265f72ac8b20c90f95b11382eb434ca2d36..d729c242366bcf59466654256b3b73f8b9759436 100644 (file)
@@ -37,10 +37,8 @@ import com.jetbrains.python.psi.types.TypeEvalContext;
 import org.jetbrains.annotations.Nls;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.HashMap;
 
 /**
  * Looks at argument lists.
@@ -108,8 +106,13 @@ public class PyArgumentListInspection extends PyInspection {
 
   public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context, int implicitOffset) {
     if (node.getParent() instanceof PyClass) return; // class Foo(object) is also an arg list
-    CallArgumentsMapping result = node.analyzeCall(PyResolveContext.noImplicits().withTypeEvalContext(context), implicitOffset);
-    final PyCallExpression.PyMarkedCallee callee = result.getMarkedCallee();
+    final PyCallExpression callExpr = node.getCallExpression();
+    if (callExpr == null) {
+      return;
+    }
+    final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
+    final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(resolveContext, implicitOffset);
+    final PyCallExpression.PyMarkedCallee callee = mapping.getMarkedCallee();
     if (callee != null) {
       final PyCallable callable = callee.getCallable();
       // Decorate functions may have different parameter lists. We don't match arguments with parameters of decorators yet
@@ -117,8 +120,8 @@ public class PyArgumentListInspection extends PyInspection {
         return;
       }
     }
-    highlightIncorrectArguments(holder, result, context);
-    highlightMissingArguments(node, holder, result);
+    highlightIncorrectArguments(callExpr, holder, mapping);
+    highlightMissingArguments(node, holder, mapping);
     highlightStarArgumentTypeMismatch(node, holder, context);
   }
 
@@ -126,50 +129,91 @@ public class PyArgumentListInspection extends PyInspection {
     inspectPyArgumentList(node, holder, context, 0);
   }
 
-  private static void highlightIncorrectArguments(ProblemsHolder holder, CallArgumentsMapping result, @NotNull TypeEvalContext context) {
-    for (Map.Entry<PyExpression, EnumSet<CallArgumentsMapping.ArgFlag>> argEntry : result.getArgumentFlags().entrySet()) {
-      EnumSet<CallArgumentsMapping.ArgFlag> flags = argEntry.getValue();
-      if (!flags.isEmpty()) { // something's wrong
-        PyExpression arg = argEntry.getKey();
-        if (flags.contains(CallArgumentsMapping.ArgFlag.IS_DUP)) {
-          holder.registerProblem(arg, PyBundle.message("INSP.duplicate.argument"), new PyRemoveArgumentQuickFix());
+  private enum ArgumentProblem {
+    OK,
+    DUPLICATE_KEYWORD_ARGUMENT,
+    DUPLICATE_KEYWORD_CONTAINER,
+    DUPLICATE_POSITIONAL_CONTAINER,
+    CANNOT_APPEAR_AFTER_KEYWORD_OR_CONTAINER,
+  }
+
+  @NotNull
+  private static Map<PyExpression, ArgumentProblem> analyzeArguments(@NotNull PyCallExpression callExpression) {
+    final Map<PyExpression, ArgumentProblem> results = new HashMap<PyExpression, ArgumentProblem>();
+    final Set<String> keywordArgumentNames = new HashSet<String>();
+    boolean seenKeywordOrContainerArgument = false;
+    boolean seenKeywordContainer = false;
+    boolean seenPositionalContainer = false;
+    for (PyExpression argument : callExpression.getArguments()) {
+      if (argument instanceof PyKeywordArgument) {
+        seenKeywordOrContainerArgument = true;
+        final String keyword = ((PyKeywordArgument)argument).getKeyword();
+        final ArgumentProblem problem;
+        if (keywordArgumentNames.contains(keyword)) {
+          problem = ArgumentProblem.DUPLICATE_KEYWORD_ARGUMENT;
         }
-        if (flags.contains(CallArgumentsMapping.ArgFlag.IS_DUP_KWD)) {
-          holder.registerProblem(arg, PyBundle.message("INSP.duplicate.doublestar.arg"), new PyRemoveArgumentQuickFix());
+        else if (seenKeywordContainer) {
+          problem = ArgumentProblem.CANNOT_APPEAR_AFTER_KEYWORD_OR_CONTAINER;
         }
-        if (flags.contains(CallArgumentsMapping.ArgFlag.IS_DUP_TUPLE)) {
-          holder.registerProblem(arg, PyBundle.message("INSP.duplicate.star.arg"), new PyRemoveArgumentQuickFix());
+        else {
+          problem = ArgumentProblem.OK;
         }
-        if (flags.contains(CallArgumentsMapping.ArgFlag.IS_POS_PAST_KWD)) {
-          holder.registerProblem(arg, PyBundle.message("INSP.cannot.appear.past.keyword.arg"), ProblemHighlightType.ERROR, new PyRemoveArgumentQuickFix());
+        results.put(argument, problem);
+        keywordArgumentNames.add(keyword);
+      }
+      else if (argument instanceof PyStarArgument) {
+        seenKeywordOrContainerArgument = true;
+        final PyStarArgument starArgument = (PyStarArgument)argument;
+        if (starArgument.isKeyword()) {
+          results.put(argument, seenKeywordContainer ? ArgumentProblem.DUPLICATE_KEYWORD_CONTAINER : ArgumentProblem.OK);
+          seenKeywordContainer = true;
         }
-        if (flags.contains(CallArgumentsMapping.ArgFlag.IS_UNMAPPED)) {
-          ArrayList<LocalQuickFix> quickFixes = Lists.<LocalQuickFix>newArrayList(new PyRemoveArgumentQuickFix());
-          if (arg instanceof PyKeywordArgument) {
-            quickFixes.add(new PyRenameArgumentQuickFix());
-          }
-          holder.registerProblem(arg, PyBundle.message("INSP.unexpected.arg"), quickFixes.toArray(new LocalQuickFix[quickFixes.size()-1]));
+        else {
+          results.put(argument, seenPositionalContainer ? ArgumentProblem.DUPLICATE_POSITIONAL_CONTAINER : ArgumentProblem.OK);
+          seenPositionalContainer = true;
         }
-        if (flags.contains(CallArgumentsMapping.ArgFlag.IS_TOO_LONG)) {
-          final PyCallExpression.PyMarkedCallee markedCallee = result.getMarkedCallee();
-          String parameterName = null;
-          if (markedCallee != null) {
-            final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), context);
-            for (int i = parameters.size() - 1; i >= 0; --i) {
-              final PyParameter param = parameters.get(i);
-              if (param instanceof PyNamedParameter) {
-                final List<PyNamedParameter> unmappedParams = result.getUnmappedParams();
-                if (!((PyNamedParameter)param).isPositionalContainer() && !((PyNamedParameter)param).isKeywordContainer() &&
-                    param.getDefaultValue() == null && !unmappedParams.contains(param)) {
-                  parameterName = param.getName();
-                  break;
-                }
-              }
-            }
-            holder.registerProblem(arg, parameterName != null ? PyBundle.message("INSP.multiple.values.resolve.to.positional.$0", parameterName)
-                                                              : PyBundle.message("INSP.more.args.that.pos.params"));
-          }
+      }
+      else {
+        results.put(argument, seenKeywordOrContainerArgument ? ArgumentProblem.CANNOT_APPEAR_AFTER_KEYWORD_OR_CONTAINER : ArgumentProblem.OK);
+      }
+    }
+    return results;
+  }
+
+  private static void highlightIncorrectArguments(@NotNull PyCallExpression callExpr,
+                                                  @NotNull ProblemsHolder holder,
+                                                  @NotNull PyCallExpression.PyArgumentsMapping mapping) {
+    final Set<PyExpression> problematicArguments = new HashSet<PyExpression>();
+    for (Map.Entry<PyExpression, ArgumentProblem> entry : analyzeArguments(callExpr).entrySet()) {
+      final PyExpression argument = entry.getKey();
+      final ArgumentProblem problem = entry.getValue();
+      switch (problem) {
+        case OK:
+          break;
+        case DUPLICATE_KEYWORD_ARGUMENT:
+          holder.registerProblem(argument, PyBundle.message("INSP.duplicate.argument"), new PyRemoveArgumentQuickFix());
+          break;
+        case DUPLICATE_KEYWORD_CONTAINER:
+          holder.registerProblem(argument, PyBundle.message("INSP.duplicate.doublestar.arg"), new PyRemoveArgumentQuickFix());
+          break;
+        case DUPLICATE_POSITIONAL_CONTAINER:
+          holder.registerProblem(argument, PyBundle.message("INSP.duplicate.star.arg"), new PyRemoveArgumentQuickFix());
+          break;
+        case CANNOT_APPEAR_AFTER_KEYWORD_OR_CONTAINER:
+          holder.registerProblem(argument, PyBundle.message("INSP.cannot.appear.past.keyword.arg"), ProblemHighlightType.ERROR, new PyRemoveArgumentQuickFix());
+      }
+      if (problem != ArgumentProblem.OK) {
+        problematicArguments.add(argument);
+      }
+    }
+
+    for (PyExpression argument : mapping.getUnmappedArguments()) {
+      if (!problematicArguments.contains(argument)) {
+        final List<LocalQuickFix> quickFixes = Lists.<LocalQuickFix>newArrayList(new PyRemoveArgumentQuickFix());
+        if (argument instanceof PyKeywordArgument) {
+          quickFixes.add(new PyRenameArgumentQuickFix());
         }
+        holder.registerProblem(argument, PyBundle.message("INSP.unexpected.arg"), quickFixes.toArray(new LocalQuickFix[quickFixes.size() - 1]));
       }
     }
   }
@@ -197,13 +241,17 @@ public class PyArgumentListInspection extends PyInspection {
     }
   }
 
-  private static void highlightMissingArguments(PyArgumentList node, ProblemsHolder holder, CallArgumentsMapping result) {
+  private static void highlightMissingArguments(@NotNull PyArgumentList node, @NotNull ProblemsHolder holder,
+                                                @NotNull PyCallExpression.PyArgumentsMapping mapping) {
     ASTNode our_node = node.getNode();
     if (our_node != null) {
       ASTNode close_paren = our_node.findChildByType(PyTokenTypes.RPAR);
       if (close_paren != null) {
-        for (PyNamedParameter param : result.getUnmappedParams()) {
-          holder.registerProblem(close_paren.getPsi(), PyBundle.message("INSP.parameter.$0.unfilled", param.getName()));
+        for (PyParameter parameter : mapping.getUnmappedParameters()) {
+          final String name = parameter.getName();
+          if (name != null) {
+            holder.registerProblem(close_paren.getPsi(), PyBundle.message("INSP.parameter.$0.unfilled", name));
+          }
         }
       }
     }
index c9ebf1f25e8375f6c169c24e88f0514095c8efdb..bbce4a658b25d5dbd30f59c80904773a7529f940 100644 (file)
@@ -90,13 +90,13 @@ public class PyCallByClassInspection extends PyInspection {
               PyClass qual_class = qual_class_type.getPyClass();
               final PyArgumentList arglist = call.getArgumentList();
               if (arglist != null) {
-                CallArgumentsMapping analysis = arglist.analyzeCall(getResolveContext());
-                final PyCallExpression.PyMarkedCallee markedCallee = analysis.getMarkedCallee();
+                final PyCallExpression.PyArgumentsMapping mapping = call.mapArguments(getResolveContext());
+                final PyCallExpression.PyMarkedCallee markedCallee = mapping.getMarkedCallee();
                 if (markedCallee != null  && markedCallee.getModifier() != STATICMETHOD) {
                   final List<PyParameter> params = PyUtil.getParameters(markedCallee.getCallable(), myTypeEvalContext);
                   if (params.size() > 0 && params.get(0) instanceof PyNamedParameter) {
                     PyNamedParameter first_param = (PyNamedParameter)params.get(0);
-                    for (Map.Entry<PyExpression, PyNamedParameter> entry : analysis.getPlainMappedParams().entrySet()) {
+                    for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getMappedParameters().entrySet()) {
                       // we ignore *arg and **arg which we cannot analyze
                       if (entry.getValue() == first_param) {
                         PyExpression first_arg = entry.getKey();
index 8237d5cbc9699c26803b2e69a0c2373a43b12124..942b2b0b08a4fb3bc117dd05d3be88cdd63cbe61 100644 (file)
@@ -25,7 +25,6 @@ import com.jetbrains.python.psi.*;
 import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
 import com.jetbrains.python.psi.types.PyNoneType;
 import com.jetbrains.python.psi.types.PyType;
-import com.jetbrains.python.psi.types.PyTypeChecker;
 import com.jetbrains.python.sdk.PySdkUtil;
 import org.jetbrains.annotations.Nls;
 import org.jetbrains.annotations.NotNull;
@@ -69,12 +68,12 @@ public class PyNoneFunctionAssignmentInspection extends PyInspection {
       final PyExpression value = node.getAssignedValue();
       if (value instanceof PyCallExpression) {
         final PyType type = myTypeEvalContext.getType(value);
-        final PyExpression callee = ((PyCallExpression)value).getCallee();
+        final PyCallExpression callExpr = (PyCallExpression)value;
+        final PyExpression callee = callExpr.getCallee();
 
         if (type instanceof PyNoneType && callee != null) {
-          final PyTypeChecker.AnalyzeCallResults analyzeCallResults = PyTypeChecker.analyzeCall(((PyCallExpression)value), myTypeEvalContext);
-          if (analyzeCallResults != null) {
-            final PyCallable callable = analyzeCallResults.getCallable();
+          final PyCallable callable = callExpr.resolveCalleeFunction(getResolveContext());
+          if (callable != null) {
             if (PySdkUtil.isElementInSkeletons(callable)) {
               return;
             }
index fa76714f1546ae91d048607e97903768437c964d..80cade67f1753c8a3aeff44e03dc8d4bd34a11ad 100644 (file)
@@ -119,9 +119,9 @@ public class PyPropertyDefinitionInspection extends PyInspection {
             assert call != null : "Property has a null call assigned to it";
             final PyArgumentList arglist = call.getArgumentList();
             assert arglist != null : "Property call has null arglist";
-            CallArgumentsMapping analysis = arglist.analyzeCall(getResolveContext());
             // we assume fget, fset, fdel, doc names
-            for (Map.Entry<PyExpression, PyNamedParameter> entry : analysis.getPlainMappedParams().entrySet()) {
+            final PyCallExpression.PyArgumentsMapping mapping = call.mapArguments(getResolveContext());
+            for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getMappedParameters().entrySet()) {
               final String paramName = entry.getValue().getName();
               PyExpression argument = PyUtil.peelArgument(entry.getKey());
               checkPropertyCallArgument(paramName, argument, node.getContainingFile());
index 509803fb2b4c303fa79257060c1819289cd1b82c..17d383f638df8c31bf43064c33629d44be6a2fb7 100644 (file)
@@ -21,8 +21,8 @@ import com.intellij.codeInspection.ProblemHighlightType;
 import com.intellij.codeInspection.ProblemsHolder;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiElementVisitor;
 import com.intellij.util.Function;
 import com.intellij.util.containers.hash.LinkedHashMap;
@@ -34,8 +34,7 @@ import org.jetbrains.annotations.Nls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author vlan
@@ -87,44 +86,60 @@ public class PyTypeCheckerInspection extends PyInspection {
     }
 
     private void checkCallSite(@Nullable PyCallSiteExpression callSite) {
-      final Map<PyGenericType, PyType> substitutions = new LinkedHashMap<PyGenericType, PyType>();
-      final PyTypeChecker.AnalyzeCallResults results = PyTypeChecker.analyzeCallSite(callSite, myTypeEvalContext);
-      if (results != null) {
-        boolean genericsCollected = false;
-        for (Map.Entry<PyExpression, PyNamedParameter> entry : results.getArguments().entrySet()) {
-          final PyNamedParameter p = entry.getValue();
-          final PyExpression key = entry.getKey();
-          if (p.isPositionalContainer() || p.isKeywordContainer()) {
-            // TODO: Support *args, **kwargs
-            continue;
-          }
-          if (p.hasDefaultValue()) {
-           final PyExpression value = p.getDefaultValue();
-            String keyName = key.getName();
-            if (key instanceof PyKeywordArgument) {
-              final PyExpression valueExpression = ((PyKeywordArgument)key).getValueExpression();
-              keyName = valueExpression != null ? valueExpression.getName() : "";
-            }
-            if (value != null && keyName != null && keyName.equals(value.getName()))
-              continue;
-          }
-          final PyType paramType = myTypeEvalContext.getType(p);
-          if (paramType == null) {
-            continue;
-          }
-          final PyType argType = myTypeEvalContext.getType(key);
-          if (!genericsCollected) {
-            substitutions.putAll(PyTypeChecker.unifyReceiver(results.getReceiver(), myTypeEvalContext));
-            genericsCollected = true;
+      final List<PyTypeChecker.AnalyzeCallResults> resultsSet = PyTypeChecker.analyzeCallSite(callSite, myTypeEvalContext);
+      final List<Map<PyExpression, Pair<String, ProblemHighlightType>>> problemsSet = new ArrayList<Map<PyExpression, Pair<String, ProblemHighlightType>>>();
+      for (PyTypeChecker.AnalyzeCallResults results : resultsSet) {
+        problemsSet.add(checkMapping(results.getReceiver(), results.getArguments()));
+      }
+      if (!problemsSet.isEmpty()) {
+        Map<PyExpression, Pair<String, ProblemHighlightType>> minProblems = Collections.min(problemsSet, new Comparator<Map<PyExpression, Pair<String, ProblemHighlightType>>>() {
+          @Override
+          public int compare(Map<PyExpression, Pair<String, ProblemHighlightType>> o1,
+                             Map<PyExpression, Pair<String, ProblemHighlightType>> o2) {
+            return o1.size() - o2.size();
           }
-          checkTypes(paramType, argType, key, myTypeEvalContext, substitutions);
+        });
+        for (Map.Entry<PyExpression, Pair<String, ProblemHighlightType>> entry : minProblems.entrySet()) {
+          registerProblem(entry.getKey(), entry.getValue().getFirst(), entry.getValue().getSecond());
+        }
+      }
+    }
+
+    @NotNull
+    private Map<PyExpression, Pair<String, ProblemHighlightType>> checkMapping(@Nullable PyExpression receiver,
+                                                                               @NotNull Map<PyExpression, PyNamedParameter> mapping) {
+      final Map<PyExpression, Pair<String, ProblemHighlightType>> problems = new HashMap<PyExpression, Pair<String, ProblemHighlightType>>();
+      final Map<PyGenericType, PyType> substitutions = new LinkedHashMap<PyGenericType, PyType>();
+      boolean genericsCollected = false;
+      for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.entrySet()) {
+        final PyNamedParameter param = entry.getValue();
+        final PyExpression arg = entry.getKey();
+        if (param.isPositionalContainer() || param.isKeywordContainer()) {
+          continue;
+        }
+        final PyType paramType = myTypeEvalContext.getType(param);
+        if (paramType == null) {
+          continue;
+        }
+        final PyType argType = myTypeEvalContext.getType(arg);
+        if (!genericsCollected) {
+          substitutions.putAll(PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext));
+          genericsCollected = true;
+        }
+        final Pair<String, ProblemHighlightType> problem = checkTypes(paramType, argType, myTypeEvalContext, substitutions);
+        if (problem != null) {
+          problems.put(arg, problem);
+
         }
       }
+      return problems;
     }
 
     @Nullable
-    private String checkTypes(@Nullable PyType expected, @Nullable PyType actual, @Nullable PsiElement node,
-                              @NotNull TypeEvalContext context, @NotNull Map<PyGenericType, PyType> substitutions) {
+    private static Pair<String, ProblemHighlightType> checkTypes(@Nullable PyType expected,
+                                                                 @Nullable PyType actual,
+                                                                 @NotNull TypeEvalContext context,
+                                                                 @NotNull Map<PyGenericType, PyType> substitutions) {
       if (actual != null && expected != null) {
         if (!PyTypeChecker.match(expected, actual, context, substitutions)) {
           final String expectedName = PythonDocumentationProvider.getTypeName(expected, context);
@@ -162,8 +177,7 @@ public class PyTypeCheckerInspection extends PyInspection {
               }
             }
           }
-          registerProblem(node, msg, highlightType);
-          return msg;
+          return Pair.create(msg, highlightType);
         }
       }
       return null;
index ed2f985af15329b9db74ff044a38adfffad4f68b..7e942a445ece7ebaea2306f171447f3fdc61399e 100644 (file)
@@ -63,8 +63,9 @@ public class PyRemoveParameterQuickFix implements LocalQuickFix {
           if (callExpression instanceof PyCallExpression) {
             final PyArgumentList argumentList = ((PyCallExpression)callExpression).getArgumentList();
             if (argumentList != null) {
-              final CallArgumentsMapping mapping = argumentList.analyzeCall(PyResolveContext.noImplicits());
-              for (Map.Entry<PyExpression, PyNamedParameter> parameterEntry : mapping.getPlainMappedParams().entrySet()) {
+              final PyResolveContext resolveContext = PyResolveContext.noImplicits();
+              final PyCallExpression.PyArgumentsMapping mapping = ((PyCallExpression)callExpression).mapArguments(resolveContext);
+              for (Map.Entry<PyExpression, PyNamedParameter> parameterEntry : mapping.getMappedParameters().entrySet()) {
                 if (parameterEntry.getValue().equals(element)) {
                   parameterEntry.getKey().delete();
                 }
index 3c872b2cf71bad0b7a64e6abd5cf690defa47eaa..befa16cf7cc745db8bf04f1e28a5a353edc01e49 100644 (file)
@@ -58,6 +58,8 @@ public class PyKnownDecoratorUtil {
     UNITTEST_EXPECTED_FAILURE("unittest.case.expectedFailure"),
     UNITTEST_MOCK_PATCH("unittest.mock.patch"),
 
+    TYPING_OVERLOAD("typing.overload"),
+
     REPRLIB_RECURSIVE_REPR("reprlib.recursive_repr");
 
     private final QualifiedName myQualifiedName;
index 727d4c686e6ce6784ed393c105f6d6bd10a74424..da93f194b54a11961de3657838af0604800e335c 100644 (file)
@@ -1579,31 +1579,68 @@ public class PyUtil {
   }
 
   @NotNull
-  public static List<PyParameter> getParameters(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
-    PyType type = context.getType(callable);
+  public static List<List<PyParameter>> getOverloadedParametersSet(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
+    final List<List<PyParameter>> parametersSet = getOverloadedParametersSet(context.getType(callable), context);
+    return parametersSet != null ? parametersSet : Collections.singletonList(Arrays.asList(callable.getParameterList().getParameters()));
+  }
+
+  @Nullable
+  private static List<PyParameter> getParametersOfCallableType(@NotNull PyCallableType type, @NotNull TypeEvalContext context) {
+    final List<PyCallableParameter> callableTypeParameters = type.getParameters(context);
+    if (callableTypeParameters != null) {
+      boolean allParametersDefined = true;
+      final List<PyParameter> parameters = new ArrayList<PyParameter>();
+      for (PyCallableParameter callableParameter : callableTypeParameters) {
+        final PyParameter parameter = callableParameter.getParameter();
+        if (parameter == null) {
+          allParametersDefined = false;
+          break;
+        }
+        parameters.add(parameter);
+      }
+      if (allParametersDefined) {
+        return parameters;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static List<List<PyParameter>> getOverloadedParametersSet(@Nullable PyType type, @NotNull TypeEvalContext context) {
     if (type instanceof PyUnionType) {
       type = ((PyUnionType)type).excludeNull(context);
     }
+
     if (type instanceof PyCallableType) {
-      final PyCallableType callableType = (PyCallableType)type;
-      final List<PyCallableParameter> callableTypeParameters = callableType.getParameters(context);
-      if (callableTypeParameters != null) {
-        boolean allParametersDefined = true;
-        final List<PyParameter> parameters = new ArrayList<PyParameter>();
-        for (PyCallableParameter callableParameter : callableTypeParameters) {
-          final PyParameter parameter = callableParameter.getParameter();
-          if (parameter == null) {
-            allParametersDefined = false;
-            break;
+      final List<PyParameter> results = getParametersOfCallableType((PyCallableType)type, context);
+      if (results != null) {
+        return Collections.singletonList(results);
+      }
+    }
+    else if (type instanceof PyUnionType) {
+      final List<List<PyParameter>> results = new ArrayList<List<PyParameter>>();
+      final Collection<PyType> members = ((PyUnionType)type).getMembers();
+      for (PyType member : members) {
+        if (member instanceof PyCallableType) {
+          final List<PyParameter> parameters = getParametersOfCallableType((PyCallableType)member, context);
+          if (parameters != null) {
+            results.add(parameters);
           }
-          parameters.add(parameter);
-        }
-        if (allParametersDefined) {
-          return parameters;
         }
       }
+      if (!results.isEmpty()) {
+        return results;
+      }
     }
-    return Arrays.asList(callable.getParameterList().getParameters());
+
+    return null;
+  }
+
+  @NotNull
+  public static List<PyParameter> getParameters(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
+    final List<List<PyParameter>> parametersSet = getOverloadedParametersSet(callable, context);
+    assert !parametersSet.isEmpty();
+    return parametersSet.get(0);
   }
 
   public static boolean isSignatureCompatibleTo(@NotNull PyCallable callable, @NotNull PyCallable otherCallable,
diff --git a/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java b/python/src/com/jetbrains/python/psi/impl/CallArgumentsMappingImpl.java
deleted file mode 100644 (file)
index 6cf66bd..0000000
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * 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.impl;
-
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.util.containers.ContainerUtil;
-import com.jetbrains.python.psi.*;
-import com.jetbrains.python.psi.types.*;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.*;
-
-/**
-* @author yole
-*/
-public class CallArgumentsMappingImpl implements CallArgumentsMapping {
-
-  private final Map<PyExpression, PyNamedParameter> myPlainMappedParams; // one param per arg
-  private final Map<PyExpression, List<PyNamedParameter>> myNestedMappedParams; // one arg sweeps a nested tuple of params
-  private PyStarArgument myTupleArg; // the *arg
-  private PyStarArgument myKwdArg;   // the **arg
-  private final List<PyNamedParameter> myTupleMappedParams; // params mapped to *arg
-  private final List<PyNamedParameter> myKwdMappedParams;   // params mapped to **arg
-  private final List<PyNamedParameter> myUnmappedParams;
-  private final Map<PyExpression, EnumSet<ArgFlag>> myArgFlags; // flags of every arg
-  private PyCallExpression.PyMarkedCallee myMarkedCallee;
-  private PyArgumentList myArgumentList;
-
-  public CallArgumentsMappingImpl(PyArgumentList arglist) {
-    // full of empty containers
-    myPlainMappedParams = new LinkedHashMap<PyExpression, PyNamedParameter>();
-    myNestedMappedParams = new LinkedHashMap<PyExpression, List<PyNamedParameter>>();
-    myTupleMappedParams = new ArrayList<PyNamedParameter>();
-    myKwdMappedParams = new ArrayList<PyNamedParameter>();
-    myUnmappedParams = new ArrayList<PyNamedParameter>();
-    myArgFlags = new HashMap<PyExpression, EnumSet<ArgFlag>>();
-    myMarkedCallee = null;
-    myArgumentList = arglist;
-  }
-
-  /**
-   * Maps arguments of a call to parameters of a callee.
-   * must contain already resolved callee with flags set appropriately.
-   * <br/>
-   * <i>NOTE:</i> <tt>*arg</tt> of unknown length is considered to be long just enough to fill appropriate
-   * positional paramaters, but at least one item long.
-   * @param arguments what to map, get if from call site
-   * @param resolved_callee what to map parameters of
-   * @param context optional shared type evaluator / cache.
-   */
-  public void mapArguments(PyCallExpression.PyMarkedCallee resolved_callee, @NotNull TypeEvalContext context) {
-    PyExpression[] arguments = myArgumentList.getArguments();
-    myMarkedCallee = resolved_callee;
-    final List<PyExpression> unmatched_subargs = new LinkedList<PyExpression>(); // unmatched nested arguments will go here
-    List<PyExpression> unmatched_args = verifyArguments();
-
-    final List<PyParameter> parameters = PyUtil.getParameters(myMarkedCallee.getCallable(), context);
-    // prepare parameter slots
-    Map<PyNamedParameter, PyExpression> slots = new LinkedHashMap<PyNamedParameter, PyExpression>();
-    PyNamedParameter kwd_par = null;   // **param
-    PyNamedParameter tuple_par = null; // *param
-    Set<PyExpression> mapped_args = new HashSet<PyExpression>();
-    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 < implicitOffset) {
-        positional_index += 1;
-        continue;
-      }
-      PyNamedParameter n_par = par.getAsNamed();
-      if (n_par != null) {
-        if (n_par.isPositionalContainer()) tuple_par = n_par;
-        else if (n_par.isKeywordContainer()) kwd_par = n_par;
-        else {
-          slots.put(n_par, null); // regular parameter that may serve as positional/named
-          if (tuple_par == null && kwd_par == null) {
-            positional_index += 1; // only if we're not past *param / **param
-          }
-        }
-      }
-      else {
-        PyTupleParameter t_par = par.getAsTuple();
-        if (t_par != null) positional_index += 1; // tuple can only be positional
-        // else lone star, skip
-      }
-      i += 1;
-    }
-    // rule out 'self' or other implicit params
-    for (i=0; i < 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 = implicitOffset;
-    int positional_bound = arguments.length; // to the right of this pos args are verboten
-    ListIterator<PyExpression> unmatched_arg_iter = unmatched_args.listIterator();
-    while (unmatched_arg_iter.hasNext()) {
-      PyExpression arg = unmatched_arg_iter.next();
-      if (arg instanceof PyStarArgument || arg instanceof PyKeywordArgument) {
-        positional_bound = cnt;
-        break;
-      }
-      if (cnt < parameters.size() && cnt < positional_index) {
-        final PyParameter par = parameters.get(cnt);
-        PyNamedParameter n_par = par.getAsNamed();
-        if (n_par != null) {
-          cnt += 1;
-          slots.put(n_par, PyUtil.peelArgument(arg));
-          mapped_args.add(arg);
-        }
-        else {
-          PyTupleParameter t_par = par.getAsTuple();
-          if (t_par != null) {
-            if (arg instanceof PyParenthesizedExpression) {
-              mapped_args.add(arg); // tuple itself is always mapped; its insides can fail
-            }
-            else {
-              PyType arg_type = context.getType(arg);
-              if (arg_type != null && arg_type.isBuiltin() && "list".equals(arg_type.getName())) {
-                mapped_args.add(arg); // we can't really analyze arbitrary lists statically yet
-                // but ListLiteralExpressions are handled by visitor
-              }
-            }
-            unmatched_arg_iter.previous();
-            MyParamVisitor visitor = new MyParamVisitor(unmatched_arg_iter, this);
-            visitor.enterTuple(t_par.getAsTuple()); // will recur as needed
-            unmatched_subargs.addAll(visitor.getUnmatchedSubargs()); // what didn't match inside
-            cnt += 1;
-          }
-          // else: goes to *param
-        }
-      }
-      else break;
-    }
-    // anything left after mapping of nested-tuple params?
-    for (Map.Entry<PyExpression, List<PyNamedParameter>> pair : myNestedMappedParams.entrySet()) {
-      PyExpression arg = pair.getKey();
-      List<PyNamedParameter> params = pair.getValue();
-      mapped_args.add(arg);
-      for (PyNamedParameter n_par : params) slots.remove(n_par);
-    }
-    for (PyExpression arg : unmatched_subargs) {
-      markArgument(arg, ArgFlag.IS_UNMAPPED);
-    }
-
-
-    boolean seen_named_args = false;
-    // map named args to named params if possible
-    Map<String, PyNamedParameter> parameter_by_name = new LinkedHashMap<String, PyNamedParameter>();
-    for (PyParameter par : parameters) {
-      PyNamedParameter n_par = par.getAsNamed();
-      if (n_par != null) parameter_by_name.put(n_par.getName(), n_par);
-    }
-    for (PyExpression arg : arguments) {
-      if (arg instanceof PyKeywordArgument) { // to explicitly named param?
-        String arg_name = ((PyKeywordArgument)arg).getKeyword();
-        PyNamedParameter respective_par = parameter_by_name.get(arg_name);
-        if (respective_par != null && !respective_par.isKeywordContainer() && !respective_par.isPositionalContainer()) {
-          if (slots.get(respective_par) != null) markArgument(arg, ArgFlag.IS_DUP);
-          else slots.put(respective_par, arg);
-        }
-        else { // to **param?
-          if (kwd_par != null) {
-            myPlainMappedParams.put(arg, kwd_par);
-            mapped_args.add(arg);
-          }
-        }
-        seen_named_args = true;
-      }
-    }
-    // map *arg to positional params if possible
-    boolean tuple_arg_not_exhausted = false;
-    boolean tuple_dup_found = false;
-    if (cnt < parameters.size() && cnt < positional_index && myTupleArg != null) {
-      // check length of myTupleArg
-      PyType tuple_arg_type = null;
-      final PyExpression expression = PsiTreeUtil.getChildOfType(myTupleArg, PyExpression.class);
-      if (expression != null) {
-        tuple_arg_type = context.getType(expression);
-      }
-      int tuple_length;
-      boolean tuple_length_known;
-      if (tuple_arg_type instanceof PyTupleType) {
-        tuple_length = ((PyTupleType)tuple_arg_type).getElementCount();
-        tuple_length_known = true;
-      }
-      else {
-        tuple_length = 2000000; // no practical function will have so many positional params
-        tuple_length_known = false;
-      }
-      int mapped_params_count = 0;
-      while (cnt < parameters.size() && cnt < positional_index && mapped_params_count < tuple_length) {
-        PyParameter par = parameters.get(cnt);
-        if (par instanceof PySingleStarParameter) break;
-        PyNamedParameter n_par = par.getAsNamed();
-        if (slots.containsKey(n_par)) {
-          final PyExpression arg_here = slots.get(n_par);
-          if (arg_here != null) {
-            if (tuple_length_known) {
-              final EnumSet<ArgFlag> flags = myArgFlags.get(arg_here);
-              if (flags == null || flags.isEmpty()) {
-                markArgument(arg_here, ArgFlag.IS_DUP);
-                tuple_dup_found = true;
-              }
-            }
-            // else: unknown tuple length is just enough
-            // the spree is over
-            break;
-          }
-          else if (n_par != null) { // normally always true
-            myTupleMappedParams.add(n_par);
-            mapped_args.add(myTupleArg);
-            slots.remove(n_par);
-          }
-        }
-        else if (n_par == tuple_par) {
-          mapped_params_count = tuple_length; // we found *param for our *arg, consider it fully mapped
-          break;
-        }
-        cnt += 1;
-        mapped_params_count += 1;
-      }
-      if (
-        tuple_length_known && (mapped_params_count < tuple_length) || // not exhausted
-        mapped_params_count == 0 // unknown length must consume at least first param
-        ) {
-        tuple_arg_not_exhausted = true;
-      }
-    }
-    // map *param to the leftmost chunk of unmapped positional args
-    // NOTE: ignores the structure of nested-tuple params!
-    if (tuple_par != null) {
-      i = 0;
-      while (i < arguments.length && mapped_args.contains(arguments[i]) && isPositionalArg(arguments[i])) {
-        i += 1; // skip first mapped args
-      }
-      if (i < arguments.length && isPositionalArg(arguments[i])) {
-        while (i < arguments.length && !mapped_args.contains(arguments[i]) && isPositionalArg(arguments[i])) {
-          myPlainMappedParams.put(arguments[i], tuple_par);
-          mapped_args.add(arguments[i]);
-          i += 1;
-        }
-      }
-    }
-    // map unmapped *arg to *param
-    if (myTupleArg != null && tuple_par != null) {
-      if (!mapped_args.contains(myTupleArg)) {
-        myTupleMappedParams.add(tuple_par);
-        mapped_args.add(myTupleArg);
-      }
-      else if (! seen_named_args && tuple_arg_not_exhausted) {
-        // case of (*(1, 2, 3)) -> (a, *b); map the rest of *arg to *param
-        myTupleMappedParams.add(tuple_par);
-        mapped_args.add(myTupleArg);
-        tuple_arg_not_exhausted = false;
-      }
-    }
-    if (tuple_arg_not_exhausted && ! tuple_dup_found) {
-      markArgument(myTupleArg, ArgFlag.IS_TOO_LONG);
-    }
-    // map unmapped named params to **kwarg
-    if (myKwdArg != null) {
-      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);
-        }
-      }
-    }
-    // map unmapped **kwarg to **param
-    if (myKwdArg != null && kwd_par != null && !mapped_args.contains(myKwdArg)) {
-      myKwdMappedParams.add(kwd_par);
-      mapped_args.add(myKwdArg);
-    }
-    // fill in ret, mark unmapped named params
-    for (Map.Entry<PyNamedParameter, PyExpression> pair : slots.entrySet()) {
-      PyNamedParameter n_par = pair.getKey();
-      PyExpression arg = pair.getValue();
-      if (arg == null) {
-        if (!n_par.hasDefaultValue()) myUnmappedParams.add(n_par);
-      }
-      else {
-        if (arg == myTupleArg) {
-          myTupleMappedParams.add(n_par);
-        }
-        else if (arg == myKwdArg) {
-          myKwdMappedParams.add(n_par);
-        }
-        else {
-          myPlainMappedParams.put(arg, n_par);
-        }
-      }
-    }
-    // mark unmapped args
-    for (PyExpression arg : slots.values()) {
-      if (arg != null) mapped_args.add(arg);
-    }
-    for (PyExpression arg : arguments) {
-      if (!mapped_args.contains(arg)) {
-        final EnumSet<ArgFlag> flags = myArgFlags.get(arg);
-        if (flags == null || flags.isEmpty()) {
-          markArgument(arg, ArgFlag.IS_UNMAPPED);
-        }
-      }
-    }
-  }
-
-  public List<PyExpression> verifyArguments() {
-    List<PyExpression> unmatched_args = new LinkedList<PyExpression>();
-    Collections.addAll(unmatched_args, myArgumentList.getArguments());
-    // detect starred args
-    for (PyExpression arg : myArgumentList.getArguments()) {
-      if (arg instanceof PyStarArgument) {
-        PyStarArgument star_arg = (PyStarArgument)arg;
-        if (star_arg.isKeyword()) {
-          if (myKwdArg == null) myKwdArg = star_arg;
-          else {
-            markArgument(arg, ArgFlag.IS_DUP_KWD);
-            unmatched_args.remove(arg);
-          }
-        }
-        else {
-          if (myTupleArg == null) myTupleArg = star_arg;
-          else {
-            markArgument(arg, ArgFlag.IS_DUP_TUPLE);
-            unmatched_args.remove(arg);
-          }
-        }
-      }
-    }
-
-    markPastBoundPositionalArguments(myArgumentList.getArguments());
-    return unmatched_args;
-  }
-
-  private void markPastBoundPositionalArguments(PyExpression[] arguments) {
-    boolean seenKwArg = false;
-    boolean seenKeyword = false;
-    boolean seenStar = false;
-    for (PyExpression arg : arguments) {
-      if (arg == myKwdArg) {
-        seenKwArg = true;
-      }
-      else if (arg instanceof PyKeywordArgument) {
-        seenKeyword = true;
-      }
-      else if (arg instanceof PyStarArgument) {
-        seenStar = true;
-      }
-
-      if (seenKeyword || seenKwArg || seenStar) {
-        if (!(arg instanceof PyStarArgument) && (seenKwArg || !(arg instanceof PyKeywordArgument))) {
-          markArgument(arg, ArgFlag.IS_POS_PAST_KWD);
-        }
-      }
-    }
-  }
-
-  private static boolean isPositionalArg(PyExpression arg) {
-    return !(arg instanceof PyKeywordArgument) && !(arg instanceof PyStarArgument);
-  }
-
-  /**
-   * @return A mapping argument->parameter for non-starred arguments (but includes starred parameters).
-   */
-  public @NotNull
-  Map<PyExpression, PyNamedParameter> getPlainMappedParams() {
-    return myPlainMappedParams;
-  }
-
-  @NotNull
-  public Map<PyExpression, List<PyNamedParameter>> getNestedMappedParams() {
-    return myNestedMappedParams;
-  }
-
-  /**
-   * @return First *arg, or null.
-   */
-  public PyStarArgument getTupleArg(){
-    return myTupleArg;
-  }
-
-  /**
-   * @return A list of parameters mapped to an *arg.
-   */
-  public @NotNull List<PyNamedParameter> getTupleMappedParams(){
-    return myTupleMappedParams;
-  }
-
-  /**
-   * @return First **arg, or null.
-   */
-  public PyStarArgument getKwdArg(){
-    return myKwdArg;
-  }
-
-  /**
-   * @return A list of parameters mapped to an **arg.
-   */
-  public @NotNull List<PyNamedParameter> getKwdMappedParams(){
-    return myKwdMappedParams;
-  }
-
-  /**
-   * @return A list of parameters for which no arguments were found ('missing').
-   */
-  public @NotNull
-  List<PyNamedParameter> getUnmappedParams(){
-    return myUnmappedParams;
-  }
-
-  /**
-   * @return result of a resolveCallee() against the function call to which the paramater list belongs.
-   */
-  @Nullable
-  public PyCallExpression.PyMarkedCallee getMarkedCallee() {
-    return myMarkedCallee;
-  }
-
-  /**
-   * @return Lists all args with their flags.
-   */
-  public Map<PyExpression, EnumSet<ArgFlag>> getArgumentFlags(){
-    return myArgFlags;
-  }
-
-  @Override
-  public boolean hasProblems() {
-    for (Map.Entry<PyExpression, EnumSet<CallArgumentsMapping.ArgFlag>> arg_entry : myArgFlags.entrySet()) {
-      EnumSet<CallArgumentsMapping.ArgFlag> flags = arg_entry.getValue();
-      if (!flags.isEmpty()) return true;
-    }
-    return myUnmappedParams.size() > 0;
-  }
-
-  public PyArgumentList getArgumentList() {
-    return myArgumentList; // that is, 'outer'
-  }
-
-  protected PyExpression markArgument(PyExpression arg, ArgFlag... flags) {
-    EnumSet<ArgFlag> argflags = myArgFlags.get(arg);
-    if (argflags == null) {
-      argflags = EnumSet.noneOf(ArgFlag.class);
-    }
-    ContainerUtil.addAll(argflags, flags);
-    myArgFlags.put(arg, argflags);
-    return arg;
-  }
-
-  static class MyParamVisitor extends PyElementVisitor {
-    private final Iterator<PyExpression> myArgIterator;
-    private final CallArgumentsMappingImpl myResult;
-    private final List<PyExpression> myUnmatchedSubargs;
-
-    private MyParamVisitor(Iterator<PyExpression> arg_iterator, CallArgumentsMappingImpl ret) {
-      myArgIterator = arg_iterator;
-      myResult = ret;
-      myUnmatchedSubargs = new ArrayList<PyExpression>(5); // arbitrary 'enough'
-    }
-
-    private Collection<PyExpression> getUnmatchedSubargs() {
-      return myUnmatchedSubargs;
-    }
-
-    @Override
-    public void visitPyParameter(PyParameter node) {
-      PyNamedParameter named = node.getAsNamed();
-      if (named != null) enterNamed(named);
-      else enterTuple(node.getAsTuple());
-    }
-
-    public void enterTuple(PyTupleParameter param) {
-      PyExpression arg = null;
-      if (myArgIterator.hasNext()) arg = myArgIterator.next();
-      // try to unpack a tuple expr in argument, if there's any
-      PyExpression[] elements = null;
-      if (arg instanceof PyParenthesizedExpression) {
-        PyExpression inner_expr = ((PyParenthesizedExpression)arg).getContainedExpression();
-        if (inner_expr instanceof PyTupleExpression) elements = ((PyTupleExpression)inner_expr).getElements();
-      }
-      else if (arg instanceof PyListLiteralExpression) {
-        elements = ((PyListLiteralExpression)arg).getElements();
-      }
-      final PyParameter[] nested_params = param.getContents();
-      if (elements != null) { // recursively map expression's tuple to parameter's.
-        final Iterator<PyExpression> subargs_iterator = Arrays.asList(elements).iterator();
-        MyParamVisitor visitor = new MyParamVisitor(subargs_iterator, myResult);
-        for (PyParameter nested : nested_params) nested.accept(visitor);
-        myUnmatchedSubargs.addAll(visitor.getUnmatchedSubargs());
-        while (subargs_iterator.hasNext()) {  // more args in a tuple than parameters
-          PyExpression overflown_arg = subargs_iterator.next();
-          myResult.markArgument(overflown_arg, ArgFlag.IS_UNMAPPED);
-        }
-      }
-      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.myNestedMappedParams.put(arg, nested_mapped);
-      }
-    }
-
-    public void enterNamed(PyNamedParameter param) {
-      if (myArgIterator.hasNext()) {
-        PyExpression subarg = myArgIterator.next();
-        myResult.myPlainMappedParams.put(subarg, param);
-      }
-      else {
-        myResult.myUnmappedParams.add(param);
-      }
-      // ...and *arg or **arg just won't parse inside a tuple, no need to handle it here
-    }
-  }
-}
index 05763cbcaec25a11ae5f53a5c190bd3151776065..f2fb4b8ad5070101fad26d90a81e9d71ad7488e5 100644 (file)
@@ -30,7 +30,6 @@ import com.jetbrains.python.PyElementTypes;
 import com.jetbrains.python.PyTokenTypes;
 import com.jetbrains.python.PythonDialectsTokenSetProvider;
 import com.jetbrains.python.psi.*;
-import com.jetbrains.python.psi.resolve.PyResolveContext;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -294,31 +293,6 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
     super.deleteChildInternal(node);
   }
 
-  @NotNull
-  public CallArgumentsMapping analyzeCall(PyResolveContext resolveContext) {
-    return analyzeCall(resolveContext, 0);
-  }
-
-  @NotNull
-  public CallArgumentsMapping analyzeCall(PyResolveContext resolveContext, int offset) {
-    final CallArgumentsMappingImpl ret = new CallArgumentsMappingImpl(this);
-    // 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) {
-      PyCallExpression.PyMarkedCallee resolvedCallee = call.resolveCallee(resolveContext, offset);
-      if (resolvedCallee != null) {
-        ret.mapArguments(resolvedCallee, resolveContext.getTypeEvalContext());
-      }
-      else {
-        ret.verifyArguments();
-      }
-    }
-    return ret;
-  }
-
   private static class NoKeyArguments extends NotNullPredicate<PyExpression> {
     @Override
     protected boolean applyNotNull(@NotNull final PyExpression input) {
index 902c2e1778e21d60b6d66d2fae5e61b336520be5..8e0c9f42869917668771cd22db6006911a8e84ec 100644 (file)
@@ -24,16 +24,15 @@ import com.intellij.psi.util.QualifiedName;
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.PyElementTypes;
 import com.jetbrains.python.PyNames;
-import com.jetbrains.python.psi.PyBinaryExpression;
-import com.jetbrains.python.psi.PyElementType;
-import com.jetbrains.python.psi.PyElementVisitor;
-import com.jetbrains.python.psi.PyExpression;
+import com.jetbrains.python.psi.*;
 import com.jetbrains.python.psi.impl.references.PyOperatorReference;
 import com.jetbrains.python.psi.resolve.PyResolveContext;
 import com.jetbrains.python.psi.types.*;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.*;
+
 /**
  * @author yole
  */
@@ -135,11 +134,38 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp
       }
       return PyUnionType.union(leftType, rightType);
     }
-    final PyTypeChecker.AnalyzeCallResults results = PyTypeChecker.analyzeCall(this, context);
-    if (results != null) {
-      final PyType type = results.getCallable().getCallType(context, this);
-      if (!PyTypeChecker.isUnknown(type) && !(type instanceof PyNoneType)) {
-        return type;
+    final List<PyTypeChecker.AnalyzeCallResults> results = PyTypeChecker.analyzeCallSite(this, context);
+    if (!results.isEmpty()) {
+      final List<PyType> types = new ArrayList<PyType>();
+      final List<PyType> matchedTypes = new ArrayList<PyType>();
+      for (PyTypeChecker.AnalyzeCallResults result : results) {
+        boolean matched = true;
+        for (Map.Entry<PyExpression, PyNamedParameter> entry : result.getArguments().entrySet()) {
+          final PyExpression argument = entry.getKey();
+          final PyNamedParameter parameter = entry.getValue();
+          if (parameter.isPositionalContainer() || parameter.isKeywordContainer()) {
+            continue;
+          }
+          final Map<PyGenericType, PyType> substitutions = new HashMap<PyGenericType, PyType>();
+          final PyType parameterType = context.getType(parameter);
+          final PyType argumentType = context.getType(argument);
+          if (!PyTypeChecker.match(parameterType, argumentType, context, substitutions)) {
+            matched = false;
+          }
+        }
+        final PyType type = result.getCallable().getCallType(context, this);
+        if (!PyTypeChecker.isUnknown(type) && !(type instanceof PyNoneType)) {
+          types.add(type);
+          if (matched) {
+            matchedTypes.add(type);
+          }
+        }
+      }
+      if (!matchedTypes.isEmpty()) {
+        return PyUnionType.union(matchedTypes);
+      }
+      if (!types.isEmpty()) {
+        return PyUnionType.union(types);
       }
     }
     if (PyNames.COMPARISON_OPERATORS.contains(getReferencedName())) {
index 9588a385d2fd4a53d60661f09221499c96e80acb..f92b968aa4aae76cbc38778041304721589b99a0 100644 (file)
@@ -43,20 +43,22 @@ import java.util.*;
  */
 public class PyCallExpressionHelper {
   private PyCallExpressionHelper() {
-    // none
   }
 
   /**
-   * TODO: Copy/Paste with {@link com.jetbrains.python.psi.PyArgumentList#addArgument(com.jetbrains.python.psi.PyExpression)}
+   * TODO: Copy/Paste with {@link PyArgumentList#addArgument(PyExpression)}
    * Adds an argument to the end of argument list.
    *
    * @param us         the arg list
    * @param expression what to add
    */
   public static void addArgument(PyCallExpression us, PyExpression expression) {
-    PyExpression[] arguments = us.getArgumentList().getArguments();
-    final PyExpression last_arg = arguments.length == 0 ? null : arguments[arguments.length - 1];
-    PyElementGenerator.getInstance(us.getProject()).insertItemIntoList(us, last_arg, expression);
+    final PyArgumentList argumentList = us.getArgumentList();
+    if (argumentList != null) {
+      final PyExpression[] arguments = argumentList.getArguments();
+      final PyExpression last_arg = arguments.length == 0 ? null : arguments[arguments.length - 1];
+      PyElementGenerator.getInstance(us.getProject()).insertItemIntoList(us, last_arg, expression);
+    }
   }
 
   /**
@@ -70,25 +72,27 @@ public class PyCallExpressionHelper {
   public static Pair<String, PyFunction> interpretAsModifierWrappingCall(PyCallExpression redefiningCall, PsiElement us) {
     PyExpression redefining_callee = redefiningCall.getCallee();
     if (redefiningCall.isCalleeText(PyNames.CLASSMETHOD, PyNames.STATICMETHOD)) {
-      final PyReferenceExpression refex = (PyReferenceExpression)redefining_callee;
-      final String refname = refex.getReferencedName();
-      if ((PyNames.CLASSMETHOD.equals(refname) || PyNames.STATICMETHOD.equals(refname))) {
-        PsiElement redefining_func = refex.getReference().resolve();
-        if (redefining_func != null) {
-          PsiElement true_func = PyBuiltinCache.getInstance(us).getByName(refname);
-          if (true_func instanceof PyClass) true_func = ((PyClass)true_func).findInitOrNew(true, null);
-          if (true_func == redefining_func) {
-            // yes, really a case of "foo = classmethod(foo)"
-            PyArgumentList arglist = redefiningCall.getArgumentList();
-            if (arglist != null) { // really can't be any other way
-              PyExpression[] args = arglist.getArguments();
-              if (args.length == 1) {
-                PyExpression possible_original_ref = args[0];
-                if (possible_original_ref instanceof PyReferenceExpression) {
-                  PsiElement original = ((PyReferenceExpression)possible_original_ref).getReference().resolve();
-                  if (original instanceof PyFunction) {
-                    // pinned down the original; replace our resolved callee with it and add flags.
-                    return Pair.create(refname, (PyFunction)original);
+      final PyReferenceExpression referenceExpr = (PyReferenceExpression)redefining_callee;
+      if (referenceExpr != null) {
+        final String refName = referenceExpr.getReferencedName();
+        if ((PyNames.CLASSMETHOD.equals(refName) || PyNames.STATICMETHOD.equals(refName))) {
+          PsiElement redefining_func = referenceExpr.getReference().resolve();
+          if (redefining_func != null) {
+            PsiElement true_func = PyBuiltinCache.getInstance(us).getByName(refName);
+            if (true_func instanceof PyClass) true_func = ((PyClass)true_func).findInitOrNew(true, null);
+            if (true_func == redefining_func) {
+              // yes, really a case of "foo = classmethod(foo)"
+              PyArgumentList argumentList = redefiningCall.getArgumentList();
+              if (argumentList != null) { // really can't be any other way
+                PyExpression[] args = argumentList.getArguments();
+                if (args.length == 1) {
+                  PyExpression possible_original_ref = args[0];
+                  if (possible_original_ref instanceof PyReferenceExpression) {
+                    PsiElement original = ((PyReferenceExpression)possible_original_ref).getReference().resolve();
+                    if (original instanceof PyFunction) {
+                      // pinned down the original; replace our resolved callee with it and add flags.
+                      return Pair.create(refName, (PyFunction)original);
+                    }
                   }
                 }
               }
@@ -105,7 +109,7 @@ public class PyCallExpressionHelper {
     PyExpression callee = us.getCallee();
 
     PsiElement resolved;
-    QualifiedResolveResult resolveResult = null;
+    QualifiedResolveResult resolveResult;
     if (callee instanceof PyReferenceExpression) {
       // dereference
       PyReferenceExpression ref = (PyReferenceExpression)callee;
@@ -200,7 +204,8 @@ public class PyCallExpressionHelper {
         else if (PyNames.STATICMETHOD.equals(wrapper_name)) wrappedModifier = PyFunction.Modifier.STATICMETHOD;
       }
     }
-    final List<PyExpression> qualifiers = resolveResult != null ? resolveResult.getQualifiers() : Collections.<PyExpression>emptyList();
+    final List<PyExpression> resolvedQualifiers = resolveResult != null ? resolveResult.getQualifiers() : null;
+    final List<PyExpression> qualifiers = resolvedQualifiers != null ?  resolvedQualifiers : Collections.<PyExpression>emptyList();
     final TypeEvalContext context = resolveContext.getTypeEvalContext();
     if (resolved instanceof PyFunction) {
       final PyFunction function = (PyFunction)resolved;
@@ -224,14 +229,13 @@ public class PyCallExpressionHelper {
       }
       boolean isByInstance = isConstructorCall || isQualifiedByInstance((PyCallable)resolved, qualifiers, context)
                              || resolved instanceof PyBoundFunction;
-      PyExpression lastQualifier = qualifiers != null && qualifiers.isEmpty() ? null : qualifiers.get(qualifiers.size() - 1);
-      boolean isByClass = lastQualifier == null ? false : isQualifiedByClass((PyCallable)resolved, lastQualifier, context);
+      final PyExpression lastQualifier = qualifiers.isEmpty() ? null : qualifiers.get(qualifiers.size() - 1);
+      boolean isByClass = lastQualifier != null && isQualifiedByClass((PyCallable)resolved, lastQualifier, context);
       final PyCallable callable = (PyCallable)resolved;
 
       implicitOffset += getImplicitArgumentCount(callable, modifier, isConstructorCall, isByInstance, isByClass);
       implicitOffset = implicitOffset < 0 ? 0 : implicitOffset; // wrong source can trigger strange behaviour
-      return new PyCallExpression.PyMarkedCallee(callable, modifier, implicitOffset,
-                                                 resolveResult != null ? resolveResult.isImplicit() : false);
+      return new PyCallExpression.PyMarkedCallee(callable, modifier, implicitOffset, resolveResult != null && resolveResult.isImplicit());
     }
     return null;
   }
@@ -247,33 +251,38 @@ public class PyCallExpressionHelper {
   }
 
   /**
-   * Calls the {@link #getImplicitArgumentCount(PyExpression, com.jetbrains.python.psi.PyCallable, com.jetbrains.python.psi.PyFunction.Modifier, EnumSet< com.jetbrains.python.psi.PyFunction.Modifier >, boolean) full version}
+   * Calls the {@link #getImplicitArgumentCount(PyCallable, PyFunction.Modifier, boolean, boolean, boolean)} full version}
    * with null flags and with isByInstance inferred directly from call site (won't work with reassigned bound methods).
    *
    * @param callReference       the call site, where arguments are given.
-   * @param functionBeingCalled resolved method which is being called; plain functions are OK but make little sense.
+   * @param function resolved method which is being called; plain functions are OK but make little sense.
    * @return a non-negative number of parameters that are implicit to this call.
    */
-  public static int getImplicitArgumentCount(
-    @NotNull final PyReferenceExpression callReference,
-    @NotNull PyFunction functionBeingCalled) {
-    //return getImplicitArgumentCount(functionBeingCalled, null, null, qualifierIsAnInstance(callReference, TypeEvalContext.fast()));
+  public static int getImplicitArgumentCount(@NotNull final PyReferenceExpression callReference, @NotNull PyFunction function,
+                                             @NotNull PyResolveContext resolveContext) {
     final PyDecorator decorator = PsiTreeUtil.getParentOfType(callReference, PyDecorator.class);
     if (decorator != null && PsiTreeUtil.isAncestor(decorator.getCallee(), callReference, false)) {
       return 1;
     }
-    final PyResolveContext resolveContext = PyResolveContext.noImplicits();
     QualifiedResolveResult followed = callReference.followAssignmentsChain(resolveContext);
-    boolean isByInstance = isQualifiedByInstance(functionBeingCalled, followed.getQualifiers(), resolveContext.getTypeEvalContext());
-    boolean isByClass = isQualifiedByInstance(functionBeingCalled, followed.getQualifiers(), resolveContext.getTypeEvalContext());
-    return getImplicitArgumentCount(functionBeingCalled, functionBeingCalled.getModifier(), false, isByInstance, isByClass);
+    final List<PyExpression> qualifiers = followed.getQualifiers();
+    final PyExpression firstQualifier = qualifiers != null && !qualifiers.isEmpty() ? qualifiers.get(0) : null;
+    boolean isByInstance = isQualifiedByInstance(function, qualifiers != null ? qualifiers : Collections.<PyExpression>emptyList(),
+                                                 resolveContext.getTypeEvalContext());
+    final boolean isConstructorCall = isConstructorName(function.getName()) &&
+                                      (!callReference.isQualified() || !isConstructorName(callReference.getName()));
+    boolean isByClass = firstQualifier != null && isQualifiedByClass(function, firstQualifier, resolveContext.getTypeEvalContext());
+    return getImplicitArgumentCount(function, function.getModifier(), isConstructorCall, isByInstance, isByClass);
+  }
+
+  private static boolean isConstructorName(@Nullable String name) {
+    return PyNames.NEW.equals(name) || PyNames.INIT.equals(name);
   }
 
   /**
    * Finds how many arguments are implicit in a given call.
    *
    * @param callable     resolved method which is being called; non-methods immediately return 0.
-   * @param flags        set of flags for the call
    * @param isByInstance true if the call is known to be by instance (not by class).
    * @return a non-negative number of parameters that are implicit to this call. E.g. for a typical method call 1 is returned
    * because one parameter ('self') is implicit.
@@ -318,7 +327,8 @@ public class PyCallExpressionHelper {
     return implicit_offset;
   }
 
-  private static boolean isQualifiedByInstance(PyCallable resolved, List<PyExpression> qualifiers, TypeEvalContext context) {
+  private static boolean isQualifiedByInstance(@Nullable PyCallable resolved, @NotNull List<PyExpression> qualifiers,
+                                               @NotNull TypeEvalContext context) {
     PyDocStringOwner owner = PsiTreeUtil.getStubOrPsiParentOfType(resolved, PyDocStringOwner.class);
     if (!(owner instanceof PyClass)) {
       return false;
@@ -328,40 +338,42 @@ public class PyCallExpressionHelper {
       return true; // unqualified + method = implicit constructor call
     }
     for (PyExpression qualifier : qualifiers) {
-      if (isQualifiedByInstance(resolved, qualifier, context)) {
+      if (qualifier != null && isQualifiedByInstance(resolved, qualifier, context)) {
         return true;
       }
     }
     return false;
   }
 
-  private static boolean isQualifiedByInstance(PyCallable resolved, PyExpression qualifier, TypeEvalContext context) {
+  private static boolean isQualifiedByInstance(@Nullable PyCallable resolved, @NotNull PyExpression qualifier,
+                                               @NotNull TypeEvalContext context) {
     if (isQualifiedByClass(resolved, qualifier, context)) {
       return false;
     }
-    PyType qtype = context.getType(qualifier);
-    if (qtype != null) {
+    final PyType qualifierType = context.getType(qualifier);
+    if (qualifierType != null) {
       // TODO: handle UnionType
-      if (qtype instanceof PyModuleType) return false; // qualified by module, not instance.
+      if (qualifierType instanceof PyModuleType) return false; // qualified by module, not instance.
     }
     return true; // NOTE. best guess: unknown qualifier is more probably an instance.
   }
 
-  private static boolean isQualifiedByClass(PyCallable resolved, PyExpression qualifier, TypeEvalContext context) {
-    PyType qtype = context.getType(qualifier);
-    if (qtype instanceof PyClassType) {
-      if (((PyClassType)qtype).isDefinition()) {
+  private static boolean isQualifiedByClass(@Nullable PyCallable resolved, @NotNull PyExpression qualifier,
+                                            @NotNull TypeEvalContext context) {
+    final PyType qualifierType = context.getType(qualifier);
+    if (qualifierType instanceof PyClassType) {
+      if (((PyClassType)qualifierType).isDefinition()) {
         PyClass resolvedParent = PsiTreeUtil.getStubOrPsiParentOfType(resolved, PyClass.class);
         if (resolvedParent != null) {
-          final PyClass qualifierClass = ((PyClassType)qtype).getPyClass();
+          final PyClass qualifierClass = ((PyClassType)qualifierType).getPyClass();
           if ((qualifierClass.isSubclass(resolvedParent) || resolvedParent.isSubclass(qualifierClass))) {
             return true;
           }
         }
       }
     }
-    else if (qtype instanceof PyClassLikeType) {
-      return ((PyClassLikeType)qtype).isDefinition(); //Any definition means callable is classmethod
+    else if (qualifierType instanceof PyClassLikeType) {
+      return ((PyClassLikeType)qualifierType).isDefinition(); //Any definition means callable is classmethod
     }
     return false;
   }
@@ -525,10 +537,6 @@ public class PyCallExpressionHelper {
       final PyCallable callable = (PyCallable)target;
       return Ref.create(callable.getCallType(context, call));
     }
-    /*PyCallExpression.PyMarkedCallee markedCallee = call.resolveCallee(PyResolveContext.defaultContext().withTypeEvalContext(context));
-    if (markedCallee != null) {
-      return Ref.create(markedCallee.getCallable().getCallType(context, call));
-    }*/
     return null;
   }
 
@@ -540,10 +548,10 @@ public class PyCallExpressionHelper {
       if (must_be_super_init instanceof PyFunction) {
         PyClass must_be_super = ((PyFunction)must_be_super_init).getContainingClass();
         if (must_be_super == PyBuiltinCache.getInstance(call).getClass(PyNames.SUPER)) {
-          PyArgumentList arglist = call.getArgumentList();
-          if (arglist != null) {
+          final PyArgumentList argumentList = call.getArgumentList();
+          if (argumentList != null) {
             final PyClass containingClass = PsiTreeUtil.getParentOfType(call, PyClass.class);
-            PyExpression[] args = arglist.getArguments();
+            PyExpression[] args = argumentList.getArguments();
             if (args.length > 1) {
               PyExpression first_arg = args[0];
               if (first_arg instanceof PyReferenceExpression) {
@@ -620,7 +628,7 @@ public class PyCallExpressionHelper {
   }
 
   /**
-   * Checks if expression callee's name matches one of names, provided by appropriate {@link com.jetbrains.python.nameResolver.FQNamesProvider}
+   * Checks if expression callee's name matches one of names, provided by appropriate {@link FQNamesProvider}
    *
    * @param expression     call expression
    * @param namesProviders name providers to check name against
@@ -631,4 +639,407 @@ public class PyCallExpressionHelper {
     final PyExpression callee = expression.getCallee();
     return (callee != null) && NameResolverTools.isName(callee, namesProviders);
   }
+
+  @NotNull
+  public static PyCallExpression.PyArgumentsMapping mapArguments(@NotNull PyCallExpression callExpression,
+                                                                 @NotNull PyResolveContext resolveContext, int implicitOffset) {
+
+    final PyArgumentList argumentList = callExpression.getArgumentList();
+    final PyCallExpression.PyMarkedCallee markedCallee = callExpression.resolveCallee(resolveContext, implicitOffset);
+
+    if (markedCallee == null || argumentList == null) {
+      return new PyCallExpression.PyArgumentsMapping(callExpression, null, Collections.<PyExpression, PyNamedParameter>emptyMap(),
+                                                     Collections.<PyParameter>emptyList(), Collections.<PyExpression>emptyList(),
+                                                     Collections.<PyNamedParameter>emptyList(), Collections.<PyNamedParameter>emptyList(),
+                                                     Collections.<PyExpression, PyTupleParameter>emptyMap());
+    }
+    final TypeEvalContext context = resolveContext.getTypeEvalContext();
+    final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), context);
+    final List<PyParameter> explicitParameters = dropImplicitParameters(parameters, markedCallee.getImplicitOffset());
+    final List<PyExpression> arguments = new ArrayList<PyExpression>(Arrays.asList(argumentList.getArguments()));
+    final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters);
+    return new PyCallExpression.PyArgumentsMapping(callExpression, markedCallee,
+                                                   mappingResults.getMappedParameters(), mappingResults.getUnmappedParameters(),
+                                                   mappingResults.getUnmappedArguments(),
+                                                   mappingResults.getParametersMappedToVariadicPositionalArguments(),
+                                                   mappingResults.getParametersMappedToVariadicKeywordArguments(),
+                                                   mappingResults.getMappedTupleParameters());
+  }
+
+  @NotNull
+  public static Map<PyExpression, PyNamedParameter> mapArguments(@NotNull List<PyExpression> arguments,
+                                                                 @NotNull List<PyParameter> parameters) {
+    return analyzeArguments(arguments, parameters).getMappedParameters();
+  }
+
+  @NotNull
+  private static ArgumentMappingResults analyzeArguments(@NotNull List<PyExpression> arguments, @NotNull List<PyParameter> parameters) {
+    boolean seenSingleStar = false;
+    boolean mappedVariadicArgumentsToParameters = false;
+    final Map<PyExpression, PyNamedParameter> mappedParameters = new LinkedHashMap<PyExpression, PyNamedParameter>();
+    final List<PyParameter> unmappedParameters = new ArrayList<PyParameter>();
+    final List<PyExpression> unmappedArguments = new ArrayList<PyExpression>();
+    final List<PyNamedParameter> parametersMappedToVariadicKeywordArguments = new ArrayList<PyNamedParameter>();
+    final List<PyNamedParameter> parametersMappedToVariadicPositionalArguments = new ArrayList<PyNamedParameter>();
+    final Map<PyExpression, PyTupleParameter> tupleMappedParameters = new LinkedHashMap<PyExpression, PyTupleParameter>();
+
+
+    final List<PyExpression> positionalArguments = filterPositionalElements(arguments);
+    final List<PyKeywordArgument> keywordArguments = filterKeywordArguments(arguments);
+    final Pair<List<PyExpression>, List<PyExpression>> variadicPositionalArgumentsAndTheirComponents = filterVariadicPositionalArguments(arguments);
+    final List<PyExpression> variadicPositionalArguments = variadicPositionalArgumentsAndTheirComponents.getFirst();
+    final Set<PyExpression> positionalComponentsOfVariadicArguments = new LinkedHashSet<PyExpression>(variadicPositionalArgumentsAndTheirComponents.getSecond());
+    final List<PyExpression> variadicKeywordArguments = filterVariadicKeywordArguments(arguments);
+
+    final List<PyExpression> allPositionalArguments = new ArrayList<PyExpression>();
+    allPositionalArguments.addAll(positionalArguments);
+    allPositionalArguments.addAll(positionalComponentsOfVariadicArguments);
+
+    for (PyParameter parameter : parameters) {
+      if (parameter instanceof PyNamedParameter) {
+        final PyNamedParameter namedParameter = (PyNamedParameter)parameter;
+        final String parameterName = namedParameter.getName();
+        if (namedParameter.isPositionalContainer()) {
+          for (PyExpression argument : allPositionalArguments) {
+            mappedParameters.put(argument, namedParameter);
+          }
+          if (variadicPositionalArguments.size() == 1) {
+            mappedParameters.put(variadicPositionalArguments.get(0), namedParameter);
+          }
+          allPositionalArguments.clear();
+          variadicPositionalArguments.clear();
+        }
+        else if (namedParameter.isKeywordContainer()) {
+          for (PyKeywordArgument argument : keywordArguments) {
+            mappedParameters.put(argument, namedParameter);
+          }
+          if (variadicKeywordArguments.size() == 1) {
+            mappedParameters.put(variadicKeywordArguments.get(0), namedParameter);
+          }
+          keywordArguments.clear();
+          variadicKeywordArguments.clear();
+        }
+        else if (seenSingleStar) {
+          final PyExpression keywordArgument = removeKeywordArgument(keywordArguments, parameterName);
+          if (keywordArgument != null) {
+            mappedParameters.put(keywordArgument, namedParameter);
+          }
+          else if (variadicKeywordArguments.isEmpty()) {
+            if (!namedParameter.hasDefaultValue()) {
+              unmappedParameters.add(namedParameter);
+            }
+          }
+          else {
+            parametersMappedToVariadicKeywordArguments.add(namedParameter);
+          }
+        }
+        else {
+          if (allPositionalArguments.isEmpty()) {
+            final PyKeywordArgument keywordArgument = removeKeywordArgument(keywordArguments, parameterName);
+            if (keywordArgument != null) {
+              mappedParameters.put(keywordArgument, namedParameter);
+            }
+            else if (variadicPositionalArguments.isEmpty() && variadicKeywordArguments.isEmpty() && !namedParameter.hasDefaultValue()) {
+              unmappedParameters.add(namedParameter);
+            }
+            else {
+              if (!variadicPositionalArguments.isEmpty()) {
+                parametersMappedToVariadicPositionalArguments.add(namedParameter);
+              }
+              if (!variadicKeywordArguments.isEmpty()) {
+                parametersMappedToVariadicKeywordArguments.add(namedParameter);
+              }
+              mappedVariadicArgumentsToParameters = true;
+            }
+          }
+          else {
+            final PyExpression positionalArgument = next(allPositionalArguments);
+            if (positionalArgument != null) {
+              mappedParameters.put(positionalArgument, namedParameter);
+              if (positionalComponentsOfVariadicArguments.contains(positionalArgument)) {
+                parametersMappedToVariadicPositionalArguments.add(namedParameter);
+              }
+            }
+            else if (!namedParameter.hasDefaultValue()) {
+              unmappedParameters.add(namedParameter);
+            }
+          }
+        }
+      }
+      else if (parameter instanceof PyTupleParameter) {
+        final PyExpression positionalArgument = next(allPositionalArguments);
+        if (positionalArgument != null) {
+          final PyTupleParameter tupleParameter = (PyTupleParameter)parameter;
+          tupleMappedParameters.put(positionalArgument, tupleParameter);
+          final TupleMappingResults tupleMappingResults = mapComponentsOfTupleParameter(positionalArgument, tupleParameter);
+          mappedParameters.putAll(tupleMappingResults.getParameters());
+          unmappedParameters.addAll(tupleMappingResults.getUnmappedParameters());
+          unmappedArguments.addAll(tupleMappingResults.getUnmappedArguments());
+        }
+        else if (variadicPositionalArguments.isEmpty()) {
+          if (!parameter.hasDefaultValue()) {
+            unmappedParameters.add(parameter);
+          }
+        }
+        else {
+          mappedVariadicArgumentsToParameters = true;
+        }
+      }
+      else if (parameter instanceof PySingleStarParameter) {
+        seenSingleStar = true;
+      }
+      else if (!parameter.hasDefaultValue()) {
+        unmappedParameters.add(parameter);
+      }
+    }
+
+    if (mappedVariadicArgumentsToParameters) {
+      variadicPositionalArguments.clear();
+      variadicKeywordArguments.clear();
+    }
+
+    unmappedArguments.addAll(allPositionalArguments);
+    unmappedArguments.addAll(keywordArguments);
+    unmappedArguments.addAll(variadicPositionalArguments);
+    unmappedArguments.addAll(variadicKeywordArguments);
+
+    return new ArgumentMappingResults(mappedParameters, unmappedParameters, unmappedArguments,
+                                      parametersMappedToVariadicPositionalArguments, parametersMappedToVariadicKeywordArguments,
+                                      tupleMappedParameters);
+  }
+
+  public static class ArgumentMappingResults {
+    @NotNull private final Map<PyExpression, PyNamedParameter> myMappedParameters;
+    @NotNull private final List<PyParameter> myUnmappedParameters;
+    @NotNull private final List<PyExpression> myUnmappedArguments;
+    @NotNull private final List<PyNamedParameter> myParametersMappedToVariadicPositionalArguments;
+    @NotNull private final List<PyNamedParameter> myParametersMappedToVariadicKeywordArguments;
+    @NotNull private final Map<PyExpression, PyTupleParameter> myMappedTupleParameters;
+
+    public ArgumentMappingResults(@NotNull Map<PyExpression, PyNamedParameter> mappedParameters,
+                                  @NotNull List<PyParameter> unmappedParameters,
+                                  @NotNull List<PyExpression> unmappedArguments,
+                                  @NotNull List<PyNamedParameter> parametersMappedToVariadicPositionalArguments,
+                                  @NotNull List<PyNamedParameter> parametersMappedToVariadicKeywordArguments,
+                                  @NotNull Map<PyExpression, PyTupleParameter> mappedTupleParameters) {
+      myMappedParameters = mappedParameters;
+      myUnmappedParameters = unmappedParameters;
+      myUnmappedArguments = unmappedArguments;
+      myParametersMappedToVariadicPositionalArguments = parametersMappedToVariadicPositionalArguments;
+      myParametersMappedToVariadicKeywordArguments = parametersMappedToVariadicKeywordArguments;
+      myMappedTupleParameters = mappedTupleParameters;
+    }
+
+    @NotNull
+    public Map<PyExpression, PyNamedParameter> getMappedParameters() {
+      return myMappedParameters;
+    }
+
+    @NotNull
+    public List<PyParameter> getUnmappedParameters() {
+      return myUnmappedParameters;
+    }
+
+    @NotNull
+    public List<PyExpression> getUnmappedArguments() {
+      return myUnmappedArguments;
+    }
+
+    @NotNull
+    public List<PyNamedParameter> getParametersMappedToVariadicPositionalArguments() {
+      return myParametersMappedToVariadicPositionalArguments;
+    }
+
+    @NotNull
+    public List<PyNamedParameter> getParametersMappedToVariadicKeywordArguments() {
+      return myParametersMappedToVariadicKeywordArguments;
+    }
+
+    @NotNull
+    public Map<PyExpression, PyTupleParameter> getMappedTupleParameters() {
+      return myMappedTupleParameters;
+    }
+  }
+
+  private static class TupleMappingResults {
+    @NotNull private final Map<PyExpression, PyNamedParameter> myParameters;
+    @NotNull private final List<PyParameter> myUnmappedParameters;
+    @NotNull private final List<PyExpression> myUnmappedArguments;
+
+    TupleMappingResults(@NotNull Map<PyExpression, PyNamedParameter> mappedParameters,
+                        @NotNull List<PyParameter> unmappedParameters,
+                        @NotNull List<PyExpression> unmappedArguments) {
+
+      myParameters = mappedParameters;
+      myUnmappedParameters = unmappedParameters;
+      myUnmappedArguments = unmappedArguments;
+    }
+
+    @NotNull
+    public Map<PyExpression, PyNamedParameter> getParameters() {
+      return myParameters;
+    }
+
+    @NotNull
+    public List<PyParameter> getUnmappedParameters() {
+      return myUnmappedParameters;
+    }
+
+    @NotNull
+    public List<PyExpression> getUnmappedArguments() {
+      return myUnmappedArguments;
+    }
+  }
+
+  @NotNull
+  private static TupleMappingResults mapComponentsOfTupleParameter(@Nullable PyExpression argument, @NotNull PyTupleParameter parameter) {
+    final List<PyParameter> unmappedParameters = new ArrayList<PyParameter>();
+    final List<PyExpression> unmappedArguments = new ArrayList<PyExpression>();
+    final Map<PyExpression, PyNamedParameter> mappedParameters = new LinkedHashMap<PyExpression, PyNamedParameter>();
+    argument = PyPsiUtils.flattenParens(argument);
+    if (argument instanceof PySequenceExpression) {
+      final PySequenceExpression sequenceExpr = (PySequenceExpression)argument;
+      final PyExpression[] argumentComponents = sequenceExpr.getElements();
+      final PyParameter[] parameterComponents = parameter.getContents();
+      for (int i = 0; i < parameterComponents.length; i++) {
+        final PyParameter param = parameterComponents[i];
+        if (i < argumentComponents.length) {
+          final PyExpression arg = argumentComponents[i];
+          if (arg != null) {
+            if (param instanceof PyNamedParameter) {
+              mappedParameters.put(arg, (PyNamedParameter)param);
+            }
+            else if (param instanceof PyTupleParameter) {
+              final TupleMappingResults nestedResults = mapComponentsOfTupleParameter(arg, (PyTupleParameter)param);
+              mappedParameters.putAll(nestedResults.getParameters());
+              unmappedParameters.addAll(nestedResults.getUnmappedParameters());
+              unmappedArguments.addAll(nestedResults.getUnmappedArguments());
+            }
+            else {
+              unmappedArguments.add(arg);
+            }
+          }
+          else {
+            unmappedParameters.add(param);
+          }
+        }
+        else {
+          unmappedParameters.add(param);
+        }
+      }
+      if (argumentComponents.length > parameterComponents.length) {
+        for (int i = parameterComponents.length; i < argumentComponents.length; i++) {
+          final PyExpression arg = argumentComponents[i];
+          if (arg != null) {
+            unmappedArguments.add(arg);
+          }
+        }
+      }
+    }
+    return new TupleMappingResults(mappedParameters, unmappedParameters, unmappedArguments);
+  }
+
+  @Nullable
+  private static PyKeywordArgument removeKeywordArgument(@NotNull List<PyKeywordArgument> arguments, @Nullable String name) {
+    PyKeywordArgument result = null;
+    for (PyKeywordArgument argument : arguments) {
+      final String keyword = argument.getKeyword();
+      if (keyword != null && keyword.equals(name)) {
+        result = argument;
+        break;
+      }
+    }
+    if (result != null) {
+      arguments.remove(result);
+    }
+    return result;
+  }
+
+  @NotNull
+  private static List<PyExpression> filterPositionalElements(@NotNull List<PyExpression> arguments) {
+    final List<PyExpression> results = new ArrayList<PyExpression>();
+    boolean seenKeywordOrContainerArgument = false;
+    for (PyExpression argument : arguments) {
+      if (isPositionalArgument(argument)) {
+        if (!seenKeywordOrContainerArgument) {
+          results.add(argument);
+        }
+      }
+      if (argument instanceof PyStarArgument || argument instanceof PyKeywordArgument) {
+        seenKeywordOrContainerArgument = true;
+      }
+    }
+    return results;
+  }
+
+  @NotNull
+  private static List<PyKeywordArgument> filterKeywordArguments(@NotNull List<PyExpression> arguments) {
+    final List<PyKeywordArgument> results = new ArrayList<PyKeywordArgument>();
+    for (PyExpression argument : arguments) {
+      if (argument instanceof PyKeywordArgument) {
+        results.add((PyKeywordArgument)argument);
+      }
+    }
+    return results;
+  }
+
+  /**
+   * Returns a list of variadic positional arguments and a list of components of variadic positional arguments
+   * if they are sequence literals.
+   */
+  @NotNull
+  private static Pair<List<PyExpression>, List<PyExpression>> filterVariadicPositionalArguments(@NotNull List<PyExpression> arguments) {
+    final List<PyExpression> variadicArguments = new ArrayList<PyExpression>();
+    final List<PyExpression> positionalComponentsOfVariadicArguments = new ArrayList<PyExpression>();
+    for (PyExpression argument : arguments) {
+      if (argument != null && isVariadicPositionalArgument(argument)) {
+        final PsiElement expr = PyPsiUtils.flattenParens(PsiTreeUtil.getChildOfType(argument, PyExpression.class));
+        if (expr instanceof PySequenceExpression) {
+          final PySequenceExpression sequenceExpr = (PySequenceExpression)expr;
+          positionalComponentsOfVariadicArguments.addAll(Arrays.asList(sequenceExpr.getElements()));
+        }
+        else {
+          variadicArguments.add(argument);
+        }
+      }
+    }
+    return Pair.create(variadicArguments, positionalComponentsOfVariadicArguments);
+  }
+
+  @NotNull
+  private static List<PyExpression> filterVariadicKeywordArguments(@NotNull List<PyExpression> arguments) {
+    final List<PyExpression> results = new ArrayList<PyExpression>();
+    for (PyExpression argument : arguments) {
+      if (argument != null && isVariadicKeywordArgument(argument)) {
+        results.add(argument);
+      }
+    }
+    return results;
+  }
+
+  public static boolean isVariadicKeywordArgument(@NotNull PyExpression argument) {
+    return argument instanceof PyStarArgument && ((PyStarArgument)argument).isKeyword();
+  }
+
+  public static boolean isVariadicPositionalArgument(@NotNull PyExpression argument) {
+    return argument instanceof PyStarArgument && !((PyStarArgument)argument).isKeyword();
+  }
+
+  @Nullable
+  private static <T> T next(@NotNull List<T> list) {
+    return list.isEmpty() ? null : list.remove(0);
+  }
+
+  @NotNull
+  private static List<PyParameter> dropImplicitParameters(@NotNull List<PyParameter> parameters, int offset) {
+    final ArrayList<PyParameter> results = new ArrayList<PyParameter>(parameters);
+    for (int i = 0; i < offset && !results.isEmpty(); i++) {
+      results.remove(0);
+    }
+    return results;
+  }
+
+  static boolean isPositionalArgument(@Nullable PyExpression argument) {
+    return !(argument instanceof PyKeywordArgument) && !(argument instanceof PyStarArgument);
+  }
 }
index 722b769da58077bc3ce2a13c635caff0ffa259b7..3067bdc5b524003556f27c9c2cfb1f9fc20ce5b8 100644 (file)
@@ -102,6 +102,18 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress
     return PyCallExpressionHelper.resolveCallee(this, resolveContext, offset);
   }
 
+  @NotNull
+  @Override
+  public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext) {
+    return PyCallExpressionHelper.mapArguments(this, resolveContext, 0);
+  }
+
+  @NotNull
+  @Override
+  public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext, int implicitOffset) {
+    return PyCallExpressionHelper.mapArguments(this, resolveContext, implicitOffset);
+  }
+
   @Override
   public boolean isCalleeText(@NotNull String... nameCandidates) {
     return PyCallExpressionHelper.isCalleeText(this, nameCandidates);
index 6aba3ea3658b2802bb38a956293d0f8dd8586701..03207ee9c7149ceeedd5e35d6b994d6e405e7b24 100644 (file)
@@ -157,6 +157,18 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
     return callee;
   }
 
+  @NotNull
+  @Override
+  public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext) {
+    return PyCallExpressionHelper.mapArguments(this, resolveContext, 0);
+  }
+
+  @NotNull
+  @Override
+  public PyArgumentsMapping mapArguments(@NotNull PyResolveContext resolveContext, int implicitOffset) {
+    return PyCallExpressionHelper.mapArguments(this, resolveContext, implicitOffset);
+  }
+
   @Override
   public PyCallable resolveCalleeFunction(PyResolveContext resolveContext) {
     return PyCallExpressionHelper.resolveCalleeFunction(this, resolveContext);
index 66e9248e076184566b06383e2266f686a8166e66..0a50b9db5e6782ed576ff158ba079b273b6c19e0 100644 (file)
@@ -43,6 +43,7 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
 import com.jetbrains.python.documentation.DocStringUtil;
 import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.resolve.PyResolveContext;
 import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
 import com.jetbrains.python.psi.stubs.PyClassStub;
 import com.jetbrains.python.psi.stubs.PyFunctionStub;
@@ -210,22 +211,20 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
   @Nullable
   @Override
   public PyType getCallType(@NotNull TypeEvalContext context, @NotNull PyCallSiteExpression callSite) {
-    PyType type = null;
     for (PyTypeProvider typeProvider : Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
-      type = typeProvider.getCallType(this, callSite, context);
+      final PyType type = typeProvider.getCallType(this, callSite, context);
       if (type != null) {
         type.assertValid(typeProvider.toString());
-        break;
+        return type;
       }
     }
-    if (type == null) {
-      type = context.getReturnType(this);
-    }
-    final PyTypeChecker.AnalyzeCallResults results = PyTypeChecker.analyzeCallSite(callSite, context);
-    if (results != null) {
-      return analyzeCallType(type, results.getReceiver(), results.getArguments(), context);
-    }
-    return type;
+    final PyExpression receiver = PyTypeChecker.getReceiver(callSite, this);
+    final List<PyExpression> arguments = PyTypeChecker.getArguments(callSite, this);
+    final List<PyParameter> parameters = PyUtil.getParameters(this, context);
+    final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
+    final List<PyParameter> explicitParameters = PyTypeChecker.filterExplicitParameters(parameters, this, callSite, resolveContext);
+    final Map<PyExpression, PyNamedParameter> mapping = PyCallExpressionHelper.mapArguments(arguments, explicitParameters);
+    return getCallType(receiver, mapping, context);
   }
 
   @Nullable
@@ -404,9 +403,8 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
         return type;
       }
     }
-    final boolean hasCustomDecorators = PyUtil.hasCustomDecorators(this) && !PyUtil.isDecoratedAsAbstract(this) && getProperty() == null;
     final PyFunctionTypeImpl type = new PyFunctionTypeImpl(this);
-    if (hasCustomDecorators) {
+    if (PyKnownDecoratorUtil.hasUnknownDecorator(this, context) && getProperty() == null) {
       return PyUnionType.createWeakType(type);
     }
     return type;
index cd604cf21500a2b5c6dd5e162f0b3db77ca46f3e..2c541faafecc87c4675ce34ba6d2fbecd0241ea1 100644 (file)
@@ -269,8 +269,8 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
               final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
               final PyArgumentList argumentList = call.getArgumentList();
               if (argumentList != null) {
-                final CallArgumentsMapping mapping = argumentList.analyzeCall(resolveContext);
-                for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getPlainMappedParams().entrySet()) {
+                final PyCallExpression.PyArgumentsMapping mapping = call.mapArguments(resolveContext);
+                for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getMappedParameters().entrySet()) {
                   if (entry.getValue() == PyNamedParameterImpl.this) {
                     final PyExpression argument = entry.getKey();
                     if (argument != null) {
@@ -393,8 +393,8 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
           }
         }
         final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
-        final CallArgumentsMapping mapping = argumentList.analyzeCall(resolveContext);
-        for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getPlainMappedParams().entrySet()) {
+        final PyCallExpression.PyArgumentsMapping mapping = callExpression.mapArguments(resolveContext);
+        for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getMappedParameters().entrySet()) {
           if (entry.getKey() == element) {
             return entry.getValue();
           }
index fd8320d1f93fe4c976153d1bcbccd1d3654f8011..d72751e1ab65bca5625931f0cfe911c2133b000a 100644 (file)
 package com.jetbrains.python.psi.types;
 
 import com.intellij.openapi.extensions.Extensions;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiPolyVariantReference;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.ResolveResult;
+import com.intellij.psi.*;
 import com.intellij.util.ArrayUtil;
 import com.jetbrains.python.PyNames;
 import com.jetbrains.python.codeInsight.PyCustomMember;
 import com.jetbrains.python.psi.*;
 import com.jetbrains.python.psi.impl.PyBuiltinCache;
+import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
 import com.jetbrains.python.psi.resolve.PyResolveContext;
 import com.jetbrains.python.psi.resolve.RatedResolveResult;
 import org.jetbrains.annotations.NotNull;
@@ -451,115 +449,55 @@ public class PyTypeChecker {
     }
   }
 
-  @Nullable
-  public static AnalyzeCallResults analyzeCall(@NotNull PyCallExpression call, @NotNull TypeEvalContext context) {
-    final PyExpression callee = call.getCallee();
-    final PyArgumentList args = call.getArgumentList();
-    if (args != null) {
-      final CallArgumentsMapping mapping = args.analyzeCall(PyResolveContext.noImplicits().withTypeEvalContext(context));
-      final Map<PyExpression, PyNamedParameter> arguments = mapping.getPlainMappedParams();
-      final PyCallExpression.PyMarkedCallee markedCallee = mapping.getMarkedCallee();
-      if (markedCallee != null) {
-        final PyCallable callable = markedCallee.getCallable();
-        if (callable instanceof PyFunction) {
-          final PyFunction function = (PyFunction)callable;
-          final PyExpression receiver;
-          if (function.getModifier() == PyFunction.Modifier.STATICMETHOD) {
-            receiver = null;
-          }
-          else if (callee instanceof PyQualifiedExpression) {
-            receiver = ((PyQualifiedExpression)callee).getQualifier();
-          }
-          else {
-            receiver = null;
-          }
-          return new AnalyzeCallResults(callable, receiver, arguments);
+  @NotNull
+  public static List<AnalyzeCallResults> analyzeCallSite(@Nullable PyCallSiteExpression callSite, @NotNull TypeEvalContext context) {
+    if (callSite != null) {
+      final List<AnalyzeCallResults> results = new ArrayList<AnalyzeCallResults>();
+      for (PyCallable callable : resolveCallee(callSite, context)) {
+        final PyExpression receiver = getReceiver(callSite, callable);
+        final List<PyExpression> arguments = getArguments(callSite, callable);
+        for (List<PyParameter> parameters : PyUtil.getOverloadedParametersSet(callable, context)) {
+          final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
+          final List<PyParameter> explicitParameters = filterExplicitParameters(parameters, callable, callSite, resolveContext);
+          final Map<PyExpression, PyNamedParameter> mapping = PyCallExpressionHelper.mapArguments(arguments, explicitParameters);
+          results.add(new AnalyzeCallResults(callable, receiver, mapping));
         }
       }
+      return results;
     }
-    return null;
+    return Collections.emptyList();
   }
 
-  @Nullable
-  public static AnalyzeCallResults analyzeCall(@NotNull PyBinaryExpression expr, @NotNull TypeEvalContext context) {
-    final PsiPolyVariantReference ref = expr.getReference(PyResolveContext.noImplicits().withTypeEvalContext(context));
-    final ResolveResult[] resolveResult;
-    resolveResult = ref.multiResolve(false);
-    AnalyzeCallResults firstResults = null;
-    for (ResolveResult result : resolveResult) {
-      final PsiElement resolved = result.getElement();
-      if (resolved instanceof PyTypedElement) {
-        final PyTypedElement typedElement = (PyTypedElement)resolved;
-        final PyType type = context.getType(typedElement);
-        if (!(type instanceof PyFunctionTypeImpl)) {
-          return null;
+  @NotNull
+  private static List<PyCallable> resolveCallee(@NotNull PyCallSiteExpression callSite, @NotNull TypeEvalContext context) {
+    final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
+    if (callSite instanceof PyCallExpression) {
+      final PyCallExpression callExpr = (PyCallExpression)callSite;
+      final PyCallExpression.PyMarkedCallee callee = callExpr.resolveCallee(resolveContext);
+      return callee != null ? Collections.singletonList(callee.getCallable()) : Collections.<PyCallable>emptyList();
+    }
+    else if (callSite instanceof PySubscriptionExpression || callSite instanceof PyBinaryExpression) {
+      final List<PyCallable> results = new ArrayList<PyCallable>();
+      boolean resolvedToUnknownResult = false;
+      for (PsiElement result : PyUtil.multiResolveTopPriority(callSite, resolveContext)) {
+        if (result instanceof PyCallable) {
+          results.add((PyCallable)result);
+          continue;
         }
-        final PyCallable callable = ((PyFunctionTypeImpl)type).getCallable();
-        final String operatorName = typedElement.getName();
-        final boolean isRight = PyNames.isRightOperatorName(operatorName);
-        final PyExpression arg = isRight ? expr.getLeftExpression() : expr.getRightExpression();
-        final PyExpression receiver = isRight ? expr.getRightExpression() : expr.getLeftExpression();
-        final PyParameter[] parameters = callable.getParameterList().getParameters();
-        if (parameters.length >= 2) {
-          final PyNamedParameter param = parameters[1].getAsNamed();
-          if (arg != null && param != null) {
-            final Map<PyExpression, PyNamedParameter> arguments = new LinkedHashMap<PyExpression, PyNamedParameter>();
-            arguments.put(arg, param);
-            final AnalyzeCallResults results = new AnalyzeCallResults(callable, receiver, arguments);
-            if (firstResults == null) {
-              firstResults = results;
-            }
-            if (match(context.getType(param), context.getType(arg), context)) {
-              return results;
-            }
+        if (result instanceof PyTypedElement) {
+          final PyType resultType = context.getType((PyTypedElement)result);
+          if (resultType instanceof PyFunctionType) {
+            results.add(((PyFunctionType)resultType).getCallable());
+            continue;
           }
         }
+        resolvedToUnknownResult = true;
       }
+      return resolvedToUnknownResult ? Collections.<PyCallable>emptyList() : results;
     }
-    if (firstResults != null) {
-      return firstResults;
-    }
-    return null;
-  }
-
-  @Nullable
-  public static AnalyzeCallResults analyzeCall(@NotNull PySubscriptionExpression expr, @NotNull TypeEvalContext context) {
-    final PsiReference ref = expr.getReference(PyResolveContext.noImplicits().withTypeEvalContext(context));
-    final PsiElement resolved;
-    resolved = ref.resolve();
-    if (resolved instanceof PyTypedElement) {
-      final PyType type = context.getType((PyTypedElement)resolved);
-      if (type instanceof PyFunctionTypeImpl) {
-        final PyCallable callable = ((PyFunctionTypeImpl)type).getCallable();
-        final PyParameter[] parameters = callable.getParameterList().getParameters();
-        if (parameters.length == 2) {
-          final PyNamedParameter param = parameters[1].getAsNamed();
-          if (param != null) {
-            final Map<PyExpression, PyNamedParameter> arguments = new LinkedHashMap<PyExpression, PyNamedParameter>();
-            final PyExpression arg = expr.getIndexExpression();
-            if (arg != null) {
-              arguments.put(arg, param);
-              return new AnalyzeCallResults(callable, expr.getOperand(), arguments);
-            }
-          }
-        }
-      }
-    }
-    return null;
-  }
-
-  @Nullable
-  public static AnalyzeCallResults analyzeCallSite(@Nullable PyCallSiteExpression callSite, @NotNull TypeEvalContext context) {
-    if (callSite instanceof PyCallExpression) {
-      return analyzeCall((PyCallExpression)callSite, context);
-    }
-    else if (callSite instanceof PyBinaryExpression) {
-      return analyzeCall((PyBinaryExpression)callSite, context);
-    }
-    else if (callSite instanceof PySubscriptionExpression) {
-      return analyzeCall((PySubscriptionExpression)callSite, context);
+    else {
+      return Collections.emptyList();
     }
-    return null;
   }
 
   @Nullable
@@ -643,6 +581,74 @@ public class PyTypeChecker {
     return null;
   }
 
+  @NotNull
+  public static List<PyParameter> filterExplicitParameters(@NotNull List<PyParameter> parameters, @NotNull PyCallable callable,
+                                                           @NotNull PyCallSiteExpression callSite,
+                                                           @NotNull PyResolveContext resolveContext) {
+    final int implicitOffset;
+    if (callSite instanceof PyCallExpression) {
+      final PyCallExpression callExpr = (PyCallExpression)callSite;
+      final PyExpression callee = callExpr.getCallee();
+      if (callee instanceof PyReferenceExpression && callable instanceof PyFunction) {
+        implicitOffset = PyCallExpressionHelper.getImplicitArgumentCount((PyReferenceExpression)callee, (PyFunction)callable,
+                                                                         resolveContext);
+      }
+      else {
+        implicitOffset = 0;
+      }
+    }
+    else if (callSite instanceof PySubscriptionExpression || callSite instanceof PyBinaryExpression) {
+      implicitOffset = 1;
+    }
+    else {
+      implicitOffset = 0;
+    }
+    return parameters.subList(Math.min(implicitOffset, parameters.size()), parameters.size());
+  }
+
+  @NotNull
+  public static List<PyExpression> getArguments(@NotNull PyCallSiteExpression expr, @NotNull PsiElement resolved) {
+    if (expr instanceof PyCallExpression) {
+      return Arrays.asList(((PyCallExpression)expr).getArguments());
+    }
+    else if (expr instanceof PySubscriptionExpression) {
+      return Collections.singletonList(((PySubscriptionExpression)expr).getIndexExpression());
+    }
+    else if (expr instanceof PyBinaryExpression) {
+      final PyBinaryExpression binaryExpr = (PyBinaryExpression)expr;
+      final boolean isRight = resolved instanceof PsiNamedElement && PyNames.isRightOperatorName(((PsiNamedElement)resolved).getName());
+      return Collections.singletonList(isRight ? binaryExpr.getLeftExpression() : binaryExpr.getRightExpression());
+    }
+    else {
+      return Collections.emptyList();
+    }
+  }
+
+  @Nullable
+  public static PyExpression getReceiver(@NotNull PyCallSiteExpression expr, @NotNull PsiElement resolved) {
+    if (expr instanceof PyCallExpression) {
+      if (resolved instanceof PyFunction) {
+        final PyFunction function = (PyFunction)resolved;
+        if (function.getModifier() == PyFunction.Modifier.STATICMETHOD) {
+          return null;
+        }
+      }
+      final PyExpression callee = ((PyCallExpression)expr).getCallee();
+      return callee instanceof PyQualifiedExpression ? ((PyQualifiedExpression)callee).getQualifier() : null;
+    }
+    else if (expr instanceof PySubscriptionExpression) {
+      return ((PySubscriptionExpression)expr).getOperand();
+    }
+    else if (expr instanceof PyBinaryExpression) {
+      final PyBinaryExpression binaryExpr = (PyBinaryExpression)expr;
+      final boolean isRight = resolved instanceof PsiNamedElement && PyNames.isRightOperatorName(((PsiNamedElement)resolved).getName());
+      return isRight ? binaryExpr.getRightExpression() : binaryExpr.getLeftExpression();
+    }
+    else {
+      return null;
+    }
+  }
+
   public static class AnalyzeCallResults {
     @NotNull private final PyCallable myCallable;
     @Nullable private final PyExpression myReceiver;
diff --git a/python/src/com/jetbrains/python/pyi/PyiClassMembersProvider.java b/python/src/com/jetbrains/python/pyi/PyiClassMembersProvider.java
new file mode 100644 (file)
index 0000000..900e8e9
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.codeInsight.PyCustomMember;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.PyTargetExpression;
+import com.jetbrains.python.psi.types.PyClassMembersProviderBase;
+import com.jetbrains.python.psi.types.PyClassType;
+import com.jetbrains.python.psi.types.PyOverridingAncestorsClassMembersProvider;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyiClassMembersProvider extends PyClassMembersProviderBase implements PyOverridingAncestorsClassMembersProvider {
+  @NotNull
+  @Override
+  public Collection<PyCustomMember> getMembers(@NotNull PyClassType classType, PsiElement location) {
+    final PyClass cls = classType.getPyClass();
+    final PsiElement pythonStub = PyiUtil.getPythonStub(cls);
+    if (pythonStub instanceof PyClass) {
+      return getClassMembers((PyClass)pythonStub);
+    }
+    return Collections.emptyList();
+  }
+
+  @Nullable
+  @Override
+  public PsiElement resolveMember(@NotNull PyClassType classType, @NotNull String name, PsiElement location,
+                                  @Nullable TypeEvalContext context) {
+    final PyClass cls = classType.getPyClass();
+    final PsiElement pythonStub = PyiUtil.getPythonStub(cls);
+    if (pythonStub instanceof PyClass) {
+      return findClassMember((PyClass)pythonStub, name);
+    }
+    return null;
+  }
+
+  private static PsiElement findClassMember(@NotNull PyClass cls, @NotNull String name) {
+    final PyFunction function = cls.findMethodByName(name, false);
+    if (function != null) {
+      return function;
+    }
+    final PyTargetExpression instanceAttribute = cls.findInstanceAttribute(name, false);
+    if (instanceAttribute != null) {
+      return instanceAttribute;
+    }
+    final PyTargetExpression classAttribute = cls.findClassAttribute(name, false);
+    if (classAttribute != null) {
+      return classAttribute;
+    }
+    return null;
+  }
+
+  private static Collection<PyCustomMember> getClassMembers(@NotNull PyClass cls) {
+    final List<PyCustomMember> result = new ArrayList<PyCustomMember>();
+    for (PyFunction function : cls.getMethods(false)) {
+      final String name = function.getName();
+      if (name != null) {
+        result.add(new PyCustomMember(name, function));
+      }
+    }
+    for (PyTargetExpression attribute : cls.getInstanceAttributes()) {
+      final String name = attribute.getName();
+      if (name != null) {
+        result.add(new PyCustomMember(name, attribute));
+      }
+    }
+    for (PyTargetExpression attribute : cls.getClassAttributes()) {
+      final String name = attribute.getName();
+      if (name != null) {
+        result.add(new PyCustomMember(name, attribute));
+      }
+    }
+    return result;
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiFile.java b/python/src/com/jetbrains/python/pyi/PyiFile.java
new file mode 100644 (file)
index 0000000..a83c3de
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.psi.FileViewProvider;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.psi.impl.PyFileImpl;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author vlan
+ */
+public class PyiFile extends PyFileImpl {
+  public PyiFile(FileViewProvider viewProvider) {
+    super(viewProvider, PyiLanguageDialect.getInstance());
+  }
+
+  @NotNull
+  @Override
+  public FileType getFileType() {
+    return PyiFileType.INSTANCE;
+  }
+
+  @Override
+  public String toString() {
+    return "PyiFile:" + getName();
+  }
+
+  @Override
+  public LanguageLevel getLanguageLevel() {
+    return LanguageLevel.PYTHON35;
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiFileElementType.java b/python/src/com/jetbrains/python/pyi/PyiFileElementType.java
new file mode 100644 (file)
index 0000000..d05f079
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.lang.Language;
+import com.jetbrains.python.psi.PyFileElementType;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author vlan
+ */
+public class PyiFileElementType extends PyFileElementType {
+  protected PyiFileElementType(Language language) {
+    super(language);
+  }
+
+  @NotNull
+  @Override
+  public String getExternalId() {
+    return "PythonStub.FILE";
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiFileType.java b/python/src/com/jetbrains/python/pyi/PyiFileType.java
new file mode 100644 (file)
index 0000000..8d629d4
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.jetbrains.python.PythonFileType;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author vlan
+ */
+public class PyiFileType extends PythonFileType {
+  public static PythonFileType INSTANCE = new PyiFileType();
+
+  protected PyiFileType() {
+    super(new PyiLanguageDialect());
+  }
+
+  @NotNull
+  @Override
+  public String getName() {
+    return PyiLanguageDialect.ID;
+  }
+
+  @NotNull
+  @Override
+  public String getDescription() {
+    return "Python stub";
+  }
+
+  @NotNull
+  @Override
+  public String getDefaultExtension() {
+    return "pyi";
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiFileTypeFactory.java b/python/src/com/jetbrains/python/pyi/PyiFileTypeFactory.java
new file mode 100644 (file)
index 0000000..52b4c06
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.openapi.fileTypes.FileTypeConsumer;
+import com.intellij.openapi.fileTypes.FileTypeFactory;
+import com.jetbrains.python.PythonFileType;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author vlan
+ */
+public class PyiFileTypeFactory extends FileTypeFactory {
+  @Override
+  public void createFileTypes(@NotNull FileTypeConsumer consumer) {
+    final PythonFileType instance = PyiFileType.INSTANCE;
+    consumer.consume(instance, instance.getDefaultExtension());
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiLanguageDialect.java b/python/src/com/jetbrains/python/pyi/PyiLanguageDialect.java
new file mode 100644 (file)
index 0000000..38b07d0
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.lang.Language;
+import com.jetbrains.python.PythonLanguage;
+
+/**
+ * @author vlan
+ */
+public class PyiLanguageDialect extends Language {
+  public static final String ID = "PythonStub";
+
+  protected PyiLanguageDialect() {
+    super(PythonLanguage.getInstance(), ID);
+  }
+
+  public static PyiLanguageDialect getInstance() {
+    return (PyiLanguageDialect)PyiFileType.INSTANCE.getLanguage();
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiModuleMembersProvider.java b/python/src/com/jetbrains/python/pyi/PyiModuleMembersProvider.java
new file mode 100644 (file)
index 0000000..3611aeb
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.codeInsight.PyCustomMember;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.resolve.PointInImport;
+import com.jetbrains.python.psi.types.PyModuleMembersProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author vlan
+ */
+public class PyiModuleMembersProvider extends PyModuleMembersProvider {
+  @Nullable
+  @Override
+  public PsiElement resolveMember(PyFile module, String name) {
+    final PsiElement pythonStub = PyiUtil.getPythonStub(module);
+    if (pythonStub instanceof PyFile) {
+      final PyFile stubFile = (PyFile)pythonStub;
+      final PsiElement member = stubFile.getElementNamed(name);
+      if (member != null && isExportedName(stubFile, name)) {
+        return member;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public Collection<PyCustomMember> getMembers(PyFile module, PointInImport point) {
+    final PsiElement pythonStub = PyiUtil.getPythonStub(module);
+    if (pythonStub instanceof PyFile) {
+      final PyFile stubFile = (PyFile)pythonStub;
+      final List<PyCustomMember> results = new ArrayList<PyCustomMember>();
+      for (PyElement element : stubFile.iterateNames()) {
+        final String name = element.getName();
+        if (name != null && isExportedName(stubFile, name)) {
+          results.add(new PyCustomMember(name, element));
+        }
+      }
+      return results;
+    }
+    return Collections.emptyList();
+  }
+
+  @Override
+  protected Collection<PyCustomMember> getMembersByQName(PyFile module, String qName) {
+    return null;
+  }
+
+  private static boolean isExportedName(@NotNull PyFile file, @NotNull String name) {
+    final PyImportElement importElement = findImportElementByName(file, name);
+    return importElement == null || isExportedImportElement(importElement);
+  }
+
+  private static boolean isExportedImportElement(@NotNull PyImportElement element) {
+    return element.getAsNameElement() != null;
+  }
+
+  @Nullable
+  private static PyImportElement findImportElementByName(@NotNull PyFile file, @NotNull String name) {
+    for (PyImportElement element : getImportElements(file)) {
+      if (name.equals(element.getVisibleName())) {
+        return element;
+      }
+    }
+    return null;
+  }
+
+  private static List<PyImportElement> getImportElements(@NotNull PyFile file) {
+    final List<PyImportElement> result = new ArrayList<PyImportElement>();
+    result.addAll(file.getImportTargets());
+    for (PyFromImportStatement statement : file.getFromImports()) {
+      result.addAll(Arrays.asList(statement.getImportElements()));
+    }
+    return result;
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiParserDefinition.java b/python/src/com/jetbrains/python/pyi/PyiParserDefinition.java
new file mode 100644 (file)
index 0000000..a62a6a7
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.tree.IFileElementType;
+import com.jetbrains.python.PythonParserDefinition;
+
+/**
+ * @author vlan
+ */
+public class PyiParserDefinition extends PythonParserDefinition {
+  public static final IFileElementType PYTHON_STUB_FILE = new PyiFileElementType(PyiLanguageDialect.getInstance());
+
+  @Override
+  public PsiFile createFile(FileViewProvider viewProvider) {
+    return new PyiFile(viewProvider);
+  }
+
+  @Override
+  public IFileElementType getFileNodeType() {
+    return PYTHON_STUB_FILE;
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiRelatedItemLineMarkerProvider.java b/python/src/com/jetbrains/python/pyi/PyiRelatedItemLineMarkerProvider.java
new file mode 100644 (file)
index 0000000..22c74fc
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.codeHighlighting.Pass;
+import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
+import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo;
+import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider;
+import com.intellij.icons.AllIcons;
+import com.intellij.navigation.GotoRelatedItem;
+import com.intellij.openapi.editor.markup.GutterIconRenderer;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.Function;
+import com.intellij.util.PsiNavigateUtil;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.PyTargetExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.event.MouseEvent;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyiRelatedItemLineMarkerProvider extends RelatedItemLineMarkerProvider {
+  // TODO: Create an icon for a related Python stub item
+  public static final Icon ICON = AllIcons.Gutter.Unique;
+
+  @Override
+  protected void collectNavigationMarkers(@NotNull PsiElement element, Collection<? super RelatedItemLineMarkerInfo> result) {
+    final PsiElement pythonStub = getPythonStub(element);
+    if (pythonStub != null) {
+      final List<GotoRelatedItem> relatedItems = GotoRelatedItem.createItems(Collections.singletonList(pythonStub));
+      result.add(new RelatedItemLineMarkerInfo<PsiElement>(
+        element, element.getTextRange(), ICON, Pass.UPDATE_OVERRIDEN_MARKERS,
+        new Function<PsiElement, String>() {
+          @Override
+          public String fun(PsiElement element) {
+            return "Has stub item in " + pythonStub.getContainingFile().getName();
+          }
+        }, new GutterIconNavigationHandler<PsiElement>() {
+          @Override
+          public void navigate(MouseEvent e, PsiElement elt) {
+            final PsiElement pythonStub = getPythonStub(elt);
+            if (pythonStub != null) {
+              PsiNavigateUtil.navigate(pythonStub);
+            }
+          }
+        }, GutterIconRenderer.Alignment.RIGHT, relatedItems));
+    }
+  }
+
+  @Nullable
+  private static PsiElement getPythonStub(@NotNull PsiElement element) {
+    if (element instanceof PyFunction || element instanceof PyTargetExpression) {
+      return PyiUtil.getPythonStub((PyElement)element);
+    }
+    return null;
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiTypeProvider.java b/python/src/com/jetbrains/python/pyi/PyiTypeProvider.java
new file mode 100644 (file)
index 0000000..e4edb9d
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.google.common.collect.ImmutableSet;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.Processor;
+import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
+import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
+import com.jetbrains.python.psi.resolve.PyResolveContext;
+import com.jetbrains.python.psi.types.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author vlan
+ */
+public class PyiTypeProvider extends PyTypeProviderBase {
+  @Override
+  public Ref<PyType> getParameterType(@NotNull PyNamedParameter param, @NotNull PyFunction func, @NotNull TypeEvalContext context) {
+    final String name = param.getName();
+    if (name != null) {
+      final PsiElement pythonStub = PyiUtil.getPythonStub(func);
+      if (pythonStub instanceof PyFunction) {
+        final PyFunction functionStub = (PyFunction)pythonStub;
+        final PyNamedParameter paramSkeleton = functionStub.getParameterList().findParameterByName(name);
+        if (paramSkeleton != null) {
+          final PyType type = context.getType(paramSkeleton);
+          if (type != null) {
+            return Ref.create(type);
+          }
+        }
+      }
+      // TODO: Allow the stub for a function to be defined as a class or a target expression alias
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public Ref<PyType> getReturnType(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
+    final PsiElement pythonStub = PyiUtil.getPythonStub(callable);
+    if (pythonStub instanceof PyCallable) {
+      final PyType type = context.getReturnType((PyCallable)pythonStub);
+      if (type != null) {
+        return Ref.create(type);
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public PyType getCallableType(@NotNull PyCallable callable, @NotNull final TypeEvalContext context) {
+    final PsiElement pythonStub = PyiUtil.getPythonStub(callable);
+    if (pythonStub instanceof PyFunction) {
+      final PyFunction functionStub = (PyFunction)pythonStub;
+      if (isOverload(functionStub, context)) {
+        return getOverloadType(functionStub, context);
+      }
+      return new PyFunctionTypeImpl(functionStub);
+    }
+    else if (callable.getContainingFile() instanceof PyiFile && callable instanceof PyFunction) {
+      final PyFunction functionStub = (PyFunction)callable;
+      if (isOverload(functionStub, context)) {
+        return getOverloadType(functionStub, context);
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public PyType getCallType(@NotNull PyFunction function, @Nullable PyCallSiteExpression callSite, @NotNull TypeEvalContext context) {
+    if (callSite != null) {
+      final PsiElement pythonStub = PyiUtil.getPythonStub(function);
+      if (pythonStub instanceof PyFunction) {
+        final PyFunction functionStub = (PyFunction)pythonStub;
+        return getOverloadedCallType(functionStub, callSite, context);
+      }
+      else if (function.getContainingFile() instanceof PyiFile) {
+        return getOverloadedCallType(function, callSite, context);
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static PyType getOverloadedCallType(@NotNull PyFunction function, @NotNull PyCallSiteExpression callSite,
+                                              @NotNull TypeEvalContext context) {
+    if (isOverload(function, context)) {
+      final List<PyType> matchedReturnTypes = new ArrayList<PyType>();
+      final List<PyType> allReturnTypes = new ArrayList<PyType>();
+      final List<PyFunction> overloads = getOverloads(function, context);
+      for (PyFunction overload : overloads) {
+        final Map<PyExpression, PyNamedParameter> mapping = mapArguments(callSite, overload, context);
+        final PyExpression receiver = PyTypeChecker.getReceiver(callSite, overload);
+        final Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyGenericCall(receiver, mapping, context);
+        final PyType returnType = context.getReturnType(overload);
+        if (!PyTypeChecker.hasGenerics(returnType, context)) {
+          allReturnTypes.add(returnType);
+        }
+        final PyType unifiedType = substitutions != null ? PyTypeChecker.substitute(returnType, substitutions, context) : null;
+        if (unifiedType != null) {
+          matchedReturnTypes.add(unifiedType);
+        }
+      }
+      return PyUnionType.union(matchedReturnTypes.isEmpty() ? allReturnTypes : matchedReturnTypes);
+    }
+    return null;
+  }
+
+  @Override
+  public PyType getReferenceType(@NotNull PsiElement target, TypeEvalContext context, @Nullable PsiElement anchor) {
+    if (target instanceof PyTargetExpression) {
+      final PsiElement pythonStub = PyiUtil.getPythonStub((PyTargetExpression)target);
+      if (pythonStub instanceof PyTypedElement) {
+        // XXX: Force the switch to AST for getting the type out of the hint in the comment
+        final TypeEvalContext effectiveContext = context.maySwitchToAST(pythonStub) ?
+                                                 context : TypeEvalContext.deepCodeInsight(target.getProject());
+        return effectiveContext.getType((PyTypedElement)pythonStub);
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static PyType getOverloadType(@NotNull PyFunction function, @NotNull final TypeEvalContext context) {
+    final List<PyFunction> overloads = getOverloads(function, context);
+    if (!overloads.isEmpty()) {
+      final List<PyType> overloadTypes = new ArrayList<PyType>();
+      for (PyFunction overload : overloads) {
+        overloadTypes.add(new PyFunctionTypeImpl(overload));
+      }
+      return PyUnionType.union(overloadTypes);
+    }
+    return null;
+  }
+
+  @NotNull
+  private static List<PyFunction> getOverloads(@NotNull PyFunction function, final @NotNull TypeEvalContext context) {
+    final ScopeOwner owner = ScopeUtil.getScopeOwner(function);
+    final String name = function.getName();
+    final List<PyFunction> overloads = new ArrayList<PyFunction>();
+    final Processor<PyFunction> overloadsProcessor = new Processor<PyFunction>() {
+      @Override
+      public boolean process(@NotNull PyFunction f) {
+        if (name != null && name.equals(f.getName()) && isOverload(f, context)) {
+          overloads.add(f);
+        }
+        return true;
+      }
+    };
+    if (owner instanceof PyClass) {
+      final PyClass cls = (PyClass)owner;
+      if (name != null) {
+        cls.visitMethods(overloadsProcessor, false);
+      }
+    }
+    else if (owner instanceof PyFile) {
+      final PyFile file = (PyFile)owner;
+      for (PyFunction f : file.getTopLevelFunctions()) {
+        if (!overloadsProcessor.process(f)) {
+          break;
+        }
+      }
+    }
+    return overloads;
+  }
+
+  private static boolean isOverload(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
+    if (callable instanceof PyDecoratable) {
+      final PyDecoratable decorated = (PyDecoratable)callable;
+      final ImmutableSet<PyKnownDecoratorUtil.KnownDecorator> decorators =
+        ImmutableSet.copyOf(PyKnownDecoratorUtil.getKnownDecorators(decorated, context));
+      if (decorators.contains(PyKnownDecoratorUtil.KnownDecorator.TYPING_OVERLOAD)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @NotNull
+  private static Map<PyExpression, PyNamedParameter> mapArguments(@NotNull PyCallSiteExpression callSite, @NotNull PsiElement resolved,
+                                                                  @NotNull TypeEvalContext context) {
+    if (resolved instanceof PyCallable) {
+      final PyCallable callable = (PyCallable)resolved;
+      final List<PyExpression> arguments = PyTypeChecker.getArguments(callSite, resolved);
+      final List<PyParameter> parameters = Arrays.asList(callable.getParameterList().getParameters());
+      final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
+      final List<PyParameter> explicitParameters = PyTypeChecker.filterExplicitParameters(parameters, callable, callSite, resolveContext);
+      return PyCallExpressionHelper.mapArguments(arguments, explicitParameters);
+    }
+    else {
+      return Collections.emptyMap();
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiUtil.java b/python/src/com/jetbrains/python/pyi/PyiUtil.java
new file mode 100644 (file)
index 0000000..1349a17
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.util.QualifiedName;
+import com.intellij.util.containers.ContainerUtil;
+import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
+import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.resolve.*;
+import com.jetbrains.python.psi.types.PyClassLikeType;
+import com.jetbrains.python.psi.types.PyType;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import com.jetbrains.python.sdk.PythonSdkType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyiUtil {
+  private PyiUtil() {}
+
+  @Nullable
+  public static PsiElement getPythonStub(@NotNull PyElement element) {
+    final PsiFile file = element.getContainingFile();
+    if (file instanceof PyFile && !(file instanceof PyiFile)) {
+      final PyiFile pythonStubFile = getPythonStubFile((PyFile)file);
+      if (pythonStubFile != null) {
+        return findStubInFile(element, pythonStubFile);
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static PyiFile getPythonStubFile(@NotNull PyFile file) {
+    final PyiFile result = findPythonStubNextToFile(file);
+    if (result != null) {
+      return result;
+    }
+    return findPythonStubInRoots(file);
+  }
+
+  @Nullable
+  private static PyiFile findPythonStubInRoots(@NotNull PyFile file) {
+    final QualifiedName qName = findImportableName(file);
+    final Sdk sdk = PythonSdkType.getSdk(file);
+    if (qName != null && sdk != null) {
+      final List<String> stubQNameComponents = ContainerUtil.newArrayList("python-stubs");
+      stubQNameComponents.addAll(qName.getComponents());
+      final QualifiedName stubQName = QualifiedName.fromComponents(stubQNameComponents);
+      final Project project = file.getProject();
+      final PythonSdkPathCache cache = PythonSdkPathCache.getInstance(project, sdk);
+      final List<PsiElement> cachedResults = cache.get(stubQName);
+      if (cachedResults != null) {
+        return getFirstPyiFile(cachedResults);
+      }
+      final ArrayList<PsiElement> results = new ArrayList<PsiElement>();
+      final PsiManager psiManager = PsiManager.getInstance(project);
+      final String nameAsPath = StringUtil.join(qName.getComponents(), "/");
+      final List<String> paths = ImmutableList.of(
+        nameAsPath + "/__init__.pyi",
+        nameAsPath + ".pyi");
+      final RootVisitor rootVisitor = new RootVisitor() {
+        @Override
+        public boolean visitRoot(VirtualFile root, @Nullable Module module, @Nullable Sdk sdk, boolean isModuleSource) {
+          if (root.isValid()) {
+            for (String path : paths) {
+              final VirtualFile pyiVirtualFile = root.findFileByRelativePath(path);
+              if (pyiVirtualFile != null) {
+                final PsiFile pyiFile = psiManager.findFile(pyiVirtualFile);
+                if (pyiFile instanceof PyiFile) {
+                  results.add(pyiFile);
+                  return false;
+                }
+              }
+            }
+          }
+          return true;
+        }
+      };
+      RootVisitorHost.visitRoots(file, rootVisitor);
+      RootVisitorHost.visitSdkRoots(sdk, rootVisitor);
+      cache.put(stubQName, results);
+      return getFirstPyiFile(results);
+    }
+    return null;
+  }
+
+  @Nullable
+  private static PyiFile getFirstPyiFile(List<PsiElement> elements) {
+    if (elements.isEmpty()) {
+      return null;
+    }
+    final PsiElement result = elements.get(0);
+    if (result instanceof PyiFile) {
+      return (PyiFile)result;
+    }
+    return null;
+  }
+
+  @Nullable
+  private static PyiFile findPythonStubNextToFile(@NotNull PyFile file) {
+    final PsiDirectory dir = file.getContainingDirectory();
+    final VirtualFile virtualFile = file.getVirtualFile();
+    if (dir != null && virtualFile != null) {
+      final String fileName = virtualFile.getNameWithoutExtension();
+      final String pythonStubFileName = fileName + "." + PyiFileType.INSTANCE.getDefaultExtension();
+      final PsiFile pythonStubFile = dir.findFile(pythonStubFileName);
+      if (pythonStubFile instanceof PyiFile) {
+        return (PyiFile)pythonStubFile;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static QualifiedName findImportableName(@NotNull PyFile file) {
+    final VirtualFile moduleVirtualFile = file.getVirtualFile();
+    if (moduleVirtualFile != null) {
+      String moduleName = QualifiedNameFinder.findShortestImportableName(file, moduleVirtualFile);
+      if (moduleName != null) {
+        final QualifiedName qName = QualifiedName.fromDottedString(moduleName);
+        for (PyCanonicalPathProvider provider : Extensions.getExtensions(PyCanonicalPathProvider.EP_NAME)) {
+          final QualifiedName restored = provider.getCanonicalPath(qName, null);
+          if (restored != null) {
+            return restored;
+          }
+        }
+        return qName;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static PsiElement findStubInFile(@NotNull PyElement element, @NotNull PyiFile file) {
+    if (element instanceof PyFile) {
+      return file;
+    }
+    final ScopeOwner owner = ScopeUtil.getScopeOwner(element);
+    final String name = element.getName();
+    if (owner != null && name != null) {
+      assert owner != element;
+      final PsiElement originalOwner = findStubInFile(owner, file);
+      if (originalOwner instanceof PyClass) {
+        final PyClass classOwner = (PyClass)originalOwner;
+        final PyType type = TypeEvalContext.codeInsightFallback(classOwner.getProject()).getType(classOwner);
+        if (type instanceof PyClassLikeType) {
+          final PyClassLikeType classType = (PyClassLikeType)type;
+          final PyClassLikeType instanceType = classType.toInstance();
+          final List<? extends RatedResolveResult> resolveResults = instanceType.resolveMember(name, null, AccessDirection.READ,
+                                                                                               PyResolveContext.noImplicits(), false);
+          if (resolveResults != null && !resolveResults.isEmpty()) {
+            return resolveResults.get(0).getElement();
+          }
+        }
+      }
+      else if (originalOwner instanceof NameDefiner) {
+        return ((NameDefiner)originalOwner).getElementNamed(name);
+      }
+    }
+    return null;
+  }
+}
diff --git a/python/src/com/jetbrains/python/pyi/PyiVisitorFilter.java b/python/src/com/jetbrains/python/pyi/PyiVisitorFilter.java
new file mode 100644 (file)
index 0000000..c10b3f9
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.psi.PsiFile;
+import com.jetbrains.python.inspections.PyStatementEffectInspection;
+import com.jetbrains.python.inspections.PyUnusedLocalInspection;
+import com.jetbrains.python.inspections.PythonVisitorFilter;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author vlan
+ */
+public class PyiVisitorFilter implements PythonVisitorFilter {
+  @Override
+  public boolean isSupported(@NotNull Class visitorClass, @NotNull PsiFile file) {
+    if (visitorClass == PyUnusedLocalInspection.class || visitorClass == PyStatementEffectInspection.class) {
+      return false;
+    }
+    return true;
+  }
+}
index 7c31767c2252cb3d9005c6218efac56bdf295334..c370343e0ee4cdf07e84ea7ea69eb651ec99fa40 100644 (file)
@@ -225,11 +225,15 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
 
     final PyArgumentList argList = PsiTreeUtil.getParentOfType(expression, PyArgumentList.class);
     if (argList != null) {
-      final CallArgumentsMapping result = argList.analyzeCall(PyResolveContext.noImplicits());
-      if (result.getMarkedCallee() != null) {
-        final PyNamedParameter namedParameter = result.getPlainMappedParams().get(expression);
-        if (namedParameter != null) {
-          candidates.add(namedParameter.getName());
+      final PyCallExpression callExpr = argList.getCallExpression();
+      if (callExpr != null) {
+        final PyResolveContext resolveContext = PyResolveContext.noImplicits();
+        final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(resolveContext);
+        if (mapping.getMarkedCallee() != null) {
+          final PyNamedParameter namedParameter = mapping.getMappedParameters().get(expression);
+          if (namedParameter != null) {
+            candidates.add(namedParameter.getName());
+          }
         }
       }
     }
index 4e33321bb5205a5455706f50ce221bb76196213e..87fcc16b238af1aed48d35deeea602ee9314fef1 100644 (file)
@@ -3,8 +3,8 @@ def f(a, b, c):
 
 f(c=1, *(10, 20))
 f(*(10, 20), c=1)
-f(*(10, 20, 30), <warning descr="Duplicate argument">c=1</warning>) # fail: duplicate c
-f(1, <warning descr="Multiple values resolve to positional parameter 'c'">*(10, 20, 30)</warning>) # fail: tuple too long
+f(*(10, 20, 30), <warning descr="Unexpected argument">c=1</warning>) # fail: duplicate c
+f(1, *(10, 20, <warning descr="Unexpected argument">30</warning>)) # fail: tuple too long
 f(1, <warning descr="Expected an iterable, got int">*(10)</warning>) # fail: wrong type
 f(1, *(10,)<warning descr="Parameter 'c' unfilled">)</warning> # fail: tuple too short, c not mapped
 
@@ -24,15 +24,14 @@ f2(*(1,2), <error descr="Cannot appear past keyword arguments or *arg or **kwarg
 def f3(a=1, b=2, c=3, *d):
   return a,b,c,d
 
-f3(c=3, <warning descr="Duplicate argument">a=1</warning>, b=2, *(1,2)) # fail: a twice
-f3(1, 2, *(3,), <warning descr="Duplicate argument">c=4</warning>) # fail: c twice
+f3(c=3, <warning descr="Unexpected argument">a=1</warning>, <warning descr="Unexpected argument">b=2</warning>, *(1,2)) # fail: a and b twice
+f3(1, 2, *(3,), <warning descr="Unexpected argument">c=4</warning>) # fail: c twice
 f3(1,2,3, *(1,2))
 f3(c=3, *(1,2)) #
-f3(1, <warning descr="Duplicate argument">c=3</warning>, *(1,2)) # fail: c twice
-f3(c=3, <warning descr="Duplicate argument">a=1</warning>, b=2, *(1,2)) # fail: a twice, no positinals
+f3(1, <warning descr="Unexpected argument">c=3</warning>, *(1,2)) # fail: c twice
 f3(c=3, a=1, b=2, <warning descr="Unexpected argument">d=(1,2)</warning>) # fail: unexpected d
 f3(1, c=3, *(10,)) # ZZZ
 f3(1, *(10,))
 f3(1, *(10,), c=20)
 f3(*(1,2), c=20)
-f3(*(1,2), <warning descr="Duplicate argument">a=20</warning>) # fail: a twice
+f3(*(1,2), <warning descr="Unexpected argument">a=20</warning>) # fail: a twice
index a43c848f77d3802ebaeb5ae4eefd5c2dc430daa0..f47c28aa899816649282e06f7da3178d438bb409 100644 (file)
@@ -19,9 +19,8 @@ def a23(a, *b, c=1):
     pass
 
 a23(1,2,3, c=10) # pass
-a23(1,2,3, c=10, <warning descr="Duplicate argument">a=1</warning>) # fail
+a23(1,2,3, c=10, <warning descr="Unexpected argument">a=1</warning>) # fail
 a23(c=10, a=1) # pass
 a23(c=10, <error descr="Cannot appear past keyword arguments or *arg or **kwarg">1</error><warning descr="Parameter 'a' unfilled">)</warning> # fail
-a23(<warning descr="Multiple values resolve to positional parameter 'a'">*args</warning>, a=1) # fail
 a23(*args, c=1) # pass
 
index 41302aae7fe88d4b6c04d73ca950bfa16050420c..d3c41a99bd97ea6cecd6bd2b9410810b8641a1af 100644 (file)
@@ -2,4 +2,4 @@ def f20(a, (b, c)):
   pass
 
 f20(1, [2, 3]) # ok
-f20(1, (2, 3, <warning descr="Unexpected argument">4</warning>)) # fail: 4 is unexpected
+f20(1, (2, 3, <warning descr="Unexpected argument">4</warning>)) # fail
diff --git a/python/testData/inspections/PyTypeCheckerInspection/ClassNew.py b/python/testData/inspections/PyTypeCheckerInspection/ClassNew.py
new file mode 100644 (file)
index 0000000..f969cfa
--- /dev/null
@@ -0,0 +1,9 @@
+class C(object):
+    def __new__(cls, name):
+        """
+        :type name: int
+        """
+        return super(cls, C).__new__(cls)
+
+
+C(<warning descr="Expected type 'int', got 'str' instead">'10'</warning>)
index 9face1ee5cf6e9e0dc68fd285c9125b50b5b4df8..2d6e638ce3b92175c64a0dce21bfdc7d09805831 100644 (file)
@@ -8,4 +8,4 @@ def f(spam, eggs):
 
 def test():
     f(<warning descr="Expected type 'list[Union[str, unicode]]', got 'list[int]' instead">[1, 2, 3]</warning>,
-      (<warning descr="Expected type 'Tuple[bool, int, unicode]', got 'Tuple[bool, int, str]' instead">False, 2, ''</warning>))
+      <warning descr="Expected type 'Tuple[bool, int, unicode]', got 'Tuple[bool, int, str]' instead">(False, 2, '')</warning>)
diff --git a/python/testData/pyi/inspections/overloads/Overloads.py b/python/testData/pyi/inspections/overloads/Overloads.py
new file mode 100644 (file)
index 0000000..af7473a
--- /dev/null
@@ -0,0 +1,36 @@
+from m1 import f, g, C, stub_only, Gen
+
+
+def test_overloaded_function(x):
+    g(<warning descr="Expected type 'dict', got 'int' instead">f(10)</warning>)
+    g(<warning descr="Expected type 'dict', got 'str' instead">f('foo')</warning>)
+    g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">f(<warning descr="Expected type 'int', got 'dict[int, int]' instead">{1: 2}</warning>)</warning>)
+    g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">f(x)</warning>)
+
+
+def test_overloaded_subscription_operator_parameters():
+    c = C()
+    print(c[10])
+    print(c['foo'])
+    print(c[<warning descr="Expected type 'int', got 'dict[int, int]' instead">{1: 2}</warning>])
+
+
+def test_overloaded_binary_operator_parameters():
+    c = C()
+    print(c + 10)
+    print(c + 'foo')
+    print(c + <warning descr="Expected type 'int', got 'dict[int, int]' instead">{1: 2}</warning>)
+
+
+def test_stub_only_function(x):
+    g(<warning descr="Expected type 'dict', got 'int' instead">stub_only(10)</warning>)
+    g(<warning descr="Expected type 'dict', got 'str' instead">stub_only('foo')</warning>)
+    g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">stub_only(x)</warning>)
+    g(<warning descr="Expected type 'dict', got 'Union[int, str]' instead">stub_only(<warning descr="Expected type 'int', got 'dict[int, int]' instead">{1: 2}</warning>)</warning>)
+
+
+def tset_overloaded_generics(x):
+    g(<warning descr="Expected type 'dict', got 'int' instead">Gen(10).get(10, 10)</warning>)
+    g(Gen(10).get(10, <weak_warning descr="Expected type 'int' (matched generic type 'T'), got 'str' instead">'foo'</weak_warning>))
+    g(Gen('foo').get(10, <weak_warning descr="Expected type 'str' (matched generic type 'T'), got 'int' instead">10</weak_warning>))
+    g(<warning descr="Expected type 'dict', got 'str' instead">Gen('foo').get(10, 'foo')</warning>)
diff --git a/python/testData/pyi/inspections/overloads/m1.py b/python/testData/pyi/inspections/overloads/m1.py
new file mode 100644 (file)
index 0000000..ffa6111
--- /dev/null
@@ -0,0 +1,22 @@
+class C(object):
+    def __getitem__(self, key):
+        return f(key)
+
+    def __add__(self, other):
+        return f(other)
+
+
+def f(key):
+    return key
+
+
+def g(x):
+    pass
+
+
+class Gen(object):
+    def __init__(self, x):
+        self.x = x
+
+    def get(self, x, y):
+        return self.x
diff --git a/python/testData/pyi/inspections/overloads/m1.pyi b/python/testData/pyi/inspections/overloads/m1.pyi
new file mode 100644 (file)
index 0000000..2adc58c
--- /dev/null
@@ -0,0 +1,39 @@
+from typing import overload, TypeVar, Generic
+
+
+@overload
+def f(key: int) -> int: ...
+@overload
+def f(key: str) -> str: ...
+
+
+def g(x: dict) -> None: ...
+
+
+@overload
+def stub_only(x: int) -> int: ...
+@overload
+def stub_only(x: str) -> str: ...
+
+
+class C:
+    @overload
+    def __getitem__(self, key: int) -> int: ...
+    @overload
+    def __getitem__(self, key: str) -> str: ...
+
+    @overload
+    def __add__(self, other: int) -> int: ...
+    @overload
+    def __add__(self, other: str) -> str: ...
+
+
+T = TypeVar('T')
+
+
+class Gen(Generic[T]):
+    def __init__(self, x: T): ...
+    @overload
+    def get(self, x: int, y: T) -> T: ...
+    @overload
+    def get(self, x: str, y: T) -> T: ...
diff --git a/python/testData/pyi/inspections/pyiStatementEffect/PyiStatementEffect.pyi b/python/testData/pyi/inspections/pyiStatementEffect/PyiStatementEffect.pyi
new file mode 100644 (file)
index 0000000..80a62af
--- /dev/null
@@ -0,0 +1 @@
+def foo() -> None: ...
diff --git a/python/testData/pyi/inspections/pyiUnusedParameters/PyiUnusedParameters.pyi b/python/testData/pyi/inspections/pyiUnusedParameters/PyiUnusedParameters.pyi
new file mode 100644 (file)
index 0000000..e0a0f03
--- /dev/null
@@ -0,0 +1 @@
+def foo(x: int, y: str) -> str: ...
diff --git a/python/testData/pyi/inspections/unresolvedClassAttributes/UnresolvedClassAttributes.py b/python/testData/pyi/inspections/unresolvedClassAttributes/UnresolvedClassAttributes.py
new file mode 100644 (file)
index 0000000..a40f25e
--- /dev/null
@@ -0,0 +1,13 @@
+from m1 import C
+
+c = C()
+
+print(c.class_field)
+print(c.instance_field)
+print(c.method())
+
+print(c.provided_class_field)
+print(c.provided_instance_field)
+print(c.provided_method)
+
+print(c.<warning descr="Unresolved attribute reference 'unresolved_attribute' for class 'C'">unresolved_attribute</warning>)
diff --git a/python/testData/pyi/inspections/unresolvedClassAttributes/m1.py b/python/testData/pyi/inspections/unresolvedClassAttributes/m1.py
new file mode 100644 (file)
index 0000000..43fe8fa
--- /dev/null
@@ -0,0 +1,8 @@
+class C:
+    class_field = 0
+
+    def __init__(self):
+        self.instance_field = 1
+
+    def method(self):
+        pass
diff --git a/python/testData/pyi/inspections/unresolvedClassAttributes/m1.pyi b/python/testData/pyi/inspections/unresolvedClassAttributes/m1.pyi
new file mode 100644 (file)
index 0000000..e88fed3
--- /dev/null
@@ -0,0 +1,7 @@
+class C:
+    provided_class_field = ...  # type: int
+
+    def __init__(self):
+        self.provided_instance_field = ...  # type: int
+
+    def provided_method(self): ...
diff --git a/python/testData/pyi/inspections/unresolvedModuleAttributes/UnresolvedModuleAttributes.py b/python/testData/pyi/inspections/unresolvedModuleAttributes/UnresolvedModuleAttributes.py
new file mode 100644 (file)
index 0000000..09aada8
--- /dev/null
@@ -0,0 +1,11 @@
+import m1
+
+
+print(m1.module_attr)
+print(m1.provided_attr)
+print(m1.<warning descr="Cannot find reference 'not_provided_attr' in 'm1.py'">not_provided_attr</warning>)
+
+
+print(m1.<warning descr="Cannot find reference 'm2' in 'm1.py'">m2</warning>)
+print(m1.<warning descr="Cannot find reference 'm3' in 'm1.py'">m3</warning>)
+print(m1.m3_imported_as)
diff --git a/python/testData/pyi/inspections/unresolvedModuleAttributes/m1.py b/python/testData/pyi/inspections/unresolvedModuleAttributes/m1.py
new file mode 100644 (file)
index 0000000..cc8e816
--- /dev/null
@@ -0,0 +1 @@
+module_attr = 0
diff --git a/python/testData/pyi/inspections/unresolvedModuleAttributes/m1.pyi b/python/testData/pyi/inspections/unresolvedModuleAttributes/m1.pyi
new file mode 100644 (file)
index 0000000..47257d9
--- /dev/null
@@ -0,0 +1,5 @@
+import m2
+import m3 as m3_imported_as
+
+
+provided_attr = ...  # int
diff --git a/python/testData/pyi/inspections/unresolvedModuleAttributes/m2.py b/python/testData/pyi/inspections/unresolvedModuleAttributes/m2.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/python/testData/pyi/inspections/unresolvedModuleAttributes/m3.py b/python/testData/pyi/inspections/unresolvedModuleAttributes/m3.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/testData/pyi/parsing/Simple.pyi b/python/testData/pyi/parsing/Simple.pyi
new file mode 100644 (file)
index 0000000..c3ae82f
--- /dev/null
@@ -0,0 +1 @@
+def foo(x: int) -> int: ...
diff --git a/python/testData/pyi/parsing/Simple.txt b/python/testData/pyi/parsing/Simple.txt
new file mode 100644 (file)
index 0000000..5520ad7
--- /dev/null
@@ -0,0 +1,29 @@
+PyiFile:Simple.pyi
+  PyFunction('foo')
+    PsiElement(Py:DEF_KEYWORD)('def')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:IDENTIFIER)('foo')
+    PyParameterList
+      PsiElement(Py:LPAR)('(')
+      PyNamedParameter('x')
+        PsiElement(Py:IDENTIFIER)('x')
+        PyAnnotation
+          PsiElement(Py:COLON)(':')
+          PsiWhiteSpace(' ')
+          PyReferenceExpression: int
+            PsiElement(Py:IDENTIFIER)('int')
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PyAnnotation
+      PsiElement(Py:RARROW)('->')
+      PsiWhiteSpace(' ')
+      PyReferenceExpression: int
+        PsiElement(Py:IDENTIFIER)('int')
+    PsiElement(Py:COLON)(':')
+    PsiWhiteSpace(' ')
+    PyStatementList
+      PyExpressionStatement
+        PyNoneLiteralExpression
+          PsiElement(Py:DOT)('.')
+          PsiElement(Py:DOT)('.')
+          PsiElement(Py:DOT)('.')
\ No newline at end of file
diff --git a/python/testData/pyi/pyiStubs/module_with_stub_in_path.pyi b/python/testData/pyi/pyiStubs/module_with_stub_in_path.pyi
new file mode 100644 (file)
index 0000000..c5d8c82
--- /dev/null
@@ -0,0 +1 @@
+def foo() -> int: ...
diff --git a/python/testData/pyi/resolve/builtinInt/BuiltinInt.pyi b/python/testData/pyi/resolve/builtinInt/BuiltinInt.pyi
new file mode 100644 (file)
index 0000000..69c5df5
--- /dev/null
@@ -0,0 +1,2 @@
+def f() -> int: ...
+#          <ref>
diff --git a/python/testData/pyi/resolve/classInsidePyiFile/ClassInsidePyiFile.pyi b/python/testData/pyi/resolve/classInsidePyiFile/ClassInsidePyiFile.pyi
new file mode 100644 (file)
index 0000000..724f115
--- /dev/null
@@ -0,0 +1,6 @@
+class C:
+    def f() -> None: ...
+
+
+def g() -> C: ...
+#          <ref>
diff --git a/python/testData/pyi/resolve/fromPyiToClassInPy/FromPyiToClassInPy.pyi b/python/testData/pyi/resolve/fromPyiToClassInPy/FromPyiToClassInPy.pyi
new file mode 100644 (file)
index 0000000..8e4e8d8
--- /dev/null
@@ -0,0 +1,5 @@
+from m1 import C
+
+
+def f(x: C) -> None: ...
+#        <ref>
diff --git a/python/testData/pyi/resolve/fromPyiToClassInPy/m1.py b/python/testData/pyi/resolve/fromPyiToClassInPy/m1.py
new file mode 100644 (file)
index 0000000..646b07a
--- /dev/null
@@ -0,0 +1,2 @@
+class C:
+    pass
diff --git a/python/testData/pyi/resolve/moduleAttribute/ModuleAttribute.py b/python/testData/pyi/resolve/moduleAttribute/ModuleAttribute.py
new file mode 100644 (file)
index 0000000..609db17
--- /dev/null
@@ -0,0 +1,5 @@
+import m1
+
+
+print(m1.foo)
+#        <ref>
diff --git a/python/testData/pyi/resolve/moduleAttribute/m1.py b/python/testData/pyi/resolve/moduleAttribute/m1.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/testData/pyi/resolve/moduleAttribute/m1.pyi b/python/testData/pyi/resolve/moduleAttribute/m1.pyi
new file mode 100644 (file)
index 0000000..7fa9046
--- /dev/null
@@ -0,0 +1 @@
+foo = ...  # int
diff --git a/python/testData/pyi/type/functionParameter/FunctionParameter.py b/python/testData/pyi/type/functionParameter/FunctionParameter.py
new file mode 100644 (file)
index 0000000..e3021fd
--- /dev/null
@@ -0,0 +1,2 @@
+def f(<caret>x):
+    pass
diff --git a/python/testData/pyi/type/functionParameter/FunctionParameter.pyi b/python/testData/pyi/type/functionParameter/FunctionParameter.pyi
new file mode 100644 (file)
index 0000000..11af3ba
--- /dev/null
@@ -0,0 +1 @@
+def f(x: int) -> None: ...
diff --git a/python/testData/pyi/type/functionReturnType/FunctionReturnType.py b/python/testData/pyi/type/functionReturnType/FunctionReturnType.py
new file mode 100644 (file)
index 0000000..4f9ce86
--- /dev/null
@@ -0,0 +1,4 @@
+def f():
+    pass
+
+<caret>x = f()
diff --git a/python/testData/pyi/type/functionReturnType/FunctionReturnType.pyi b/python/testData/pyi/type/functionReturnType/FunctionReturnType.pyi
new file mode 100644 (file)
index 0000000..bc3afe2
--- /dev/null
@@ -0,0 +1,4 @@
+from typing import Optional
+
+
+def f() -> Optional[int]: ...
diff --git a/python/testData/pyi/type/functionType/FunctionType.py b/python/testData/pyi/type/functionType/FunctionType.py
new file mode 100644 (file)
index 0000000..3a1535b
--- /dev/null
@@ -0,0 +1,2 @@
+def <caret>f(x):
+    pass
diff --git a/python/testData/pyi/type/functionType/FunctionType.pyi b/python/testData/pyi/type/functionType/FunctionType.pyi
new file mode 100644 (file)
index 0000000..c673ee0
--- /dev/null
@@ -0,0 +1 @@
+def f(x: int) -> dict: ...
diff --git a/python/testData/pyi/type/moduleAttribute/ModuleAttribute.py b/python/testData/pyi/type/moduleAttribute/ModuleAttribute.py
new file mode 100644 (file)
index 0000000..5c72574
--- /dev/null
@@ -0,0 +1 @@
+<caret>x = None
diff --git a/python/testData/pyi/type/moduleAttribute/ModuleAttribute.pyi b/python/testData/pyi/type/moduleAttribute/ModuleAttribute.pyi
new file mode 100644 (file)
index 0000000..7101c89
--- /dev/null
@@ -0,0 +1 @@
+x = ...  # type: int
diff --git a/python/testData/pyi/type/overloadedReturnType/OverloadedReturnType.py b/python/testData/pyi/type/overloadedReturnType/OverloadedReturnType.py
new file mode 100644 (file)
index 0000000..9497c2e
--- /dev/null
@@ -0,0 +1,5 @@
+def f(x):
+    pass
+
+
+<caret>x = f('foo')
diff --git a/python/testData/pyi/type/overloadedReturnType/OverloadedReturnType.pyi b/python/testData/pyi/type/overloadedReturnType/OverloadedReturnType.pyi
new file mode 100644 (file)
index 0000000..2cf9eb9
--- /dev/null
@@ -0,0 +1,7 @@
+from typing import overload
+
+
+@overload
+def f(x: int) -> int: ...
+@overload
+def f(x: str) -> str: ...
diff --git a/python/testData/pyi/type/pyiOnPythonPath/PyiOnPythonPath.py b/python/testData/pyi/type/pyiOnPythonPath/PyiOnPythonPath.py
new file mode 100644 (file)
index 0000000..25559c4
--- /dev/null
@@ -0,0 +1,4 @@
+from module_with_stub_in_path import foo
+
+
+<caret>expr = foo()
diff --git a/python/testData/pyi/type/pyiOnPythonPath/module_with_stub_in_path.py b/python/testData/pyi/type/pyiOnPythonPath/module_with_stub_in_path.py
new file mode 100644 (file)
index 0000000..9332a27
--- /dev/null
@@ -0,0 +1,2 @@
+def foo():
+    pass
index 8110ae02e8287b35b98294ddc751863040681e3b..edea7b565a85e0749824e7f9921d675a3dbfea08 100644 (file)
@@ -1,2 +1,2 @@
 def ones(shape, dtype=None, order='C')
-Inferred type: (shape:&nbsp;Union[<a href="psi_element://#typename#int">int</a>,&nbsp;Iterable[<a href="psi_element://#typename#int">int</a>]],&nbsp;dtype:&nbsp;<a href="psi_element://#typename#object">object</a>,&nbsp;order:&nbsp;<a href="psi_element://#typename#str">str</a>)&nbsp;-&gt;&nbsp;ndarray<br>
\ No newline at end of file
+Inferred type: (shape:&nbsp;Union[<a href="psi_element://#typename#int">int</a>,&nbsp;Iterable[<a href="psi_element://#typename#int">int</a>]],&nbsp;dtype:&nbsp;Optional[<a href="psi_element://#typename#object">object</a>],&nbsp;order:&nbsp;Optional[<a href="psi_element://#typename#str">str</a>])&nbsp;-&gt;&nbsp;ndarray<br>
\ No newline at end of file
index 2d311e59d2e35819031d59184a1790b3cc160571..08c4181f7ece19ade3a50fd51b1f7f371e12a874 100644 (file)
@@ -29,8 +29,8 @@ import com.intellij.util.Function;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.HashSet;
 import com.jetbrains.python.fixtures.LightMarkedTestCase;
-import com.jetbrains.python.psi.CallArgumentsMapping;
 import com.jetbrains.python.psi.PyArgumentList;
+import com.jetbrains.python.psi.PyCallExpression;
 import junit.framework.Assert;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -422,7 +422,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
     if (collector.getParameterOwner() != null) {
       Assert.assertEquals("Collected one analysis result", 1, collector.myItems.length);
       handler.updateParameterInfo((PyArgumentList)collector.getParameterOwner(), collector); // moves offset to correct parameter
-      handler.updateUI((CallArgumentsMapping)collector.getItemsToShow()[0], collector); // sets hint text and flags
+      handler.updateUI((PyCallExpression.PyArgumentsMapping)collector.getItemsToShow()[0], collector); // sets hint text and flags
     }
     return collector;
   }
index e3279805a97fc326e1e279b6f9bcd9ae0c17d56d..936e2abfdc920f260e80c910f4e4a604cf450e03 100644 (file)
@@ -63,7 +63,7 @@ public abstract class PyMultiFileResolveTestCase extends PyResolveTestCase {
   protected PsiFile prepareFile() {
     prepareTestDirectory();
     VirtualFile sourceFile = null;
-    for (String ext : new String[]{".py", ".pyx"}) {
+    for (String ext : new String[]{".py", ".pyx", ".pyi"}) {
       final String fileName = myTestFileName != null ? myTestFileName : getTestName(false) + ext;
       sourceFile = myFixture.findFileInTempDir(fileName);
       if (sourceFile != null) {
index 4bf051596378e574f791bbd2932131e207320fe1..3418953447b940d85dac1716f888a12aba8705b9 100644 (file)
@@ -271,4 +271,8 @@ public class PyTypeCheckerInspectionTest extends PyTestCase {
   public void testComparisonOperatorsForNumericTypes() {
     doTest();
   }
+
+  public void testClassNew() {
+    doTest();
+  }
 }
diff --git a/python/testSrc/com/jetbrains/python/pyi/PyiInspectionsTest.java b/python/testSrc/com/jetbrains/python/pyi/PyiInspectionsTest.java
new file mode 100644 (file)
index 0000000..9b465e2
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.codeInspection.LocalInspectionTool;
+import com.intellij.psi.PsiDocumentManager;
+import com.jetbrains.python.fixtures.PyTestCase;
+import com.jetbrains.python.inspections.PyStatementEffectInspection;
+import com.jetbrains.python.inspections.PyTypeCheckerInspection;
+import com.jetbrains.python.inspections.PyUnusedLocalInspection;
+import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author vlan
+ */
+public class PyiInspectionsTest extends PyTestCase {
+  private void doTest(@NotNull Class<? extends LocalInspectionTool> inspectionClass, @NotNull String extension) {
+    myFixture.copyDirectoryToProject("pyi/inspections/" + getTestName(true), "");
+    myFixture.copyDirectoryToProject("typing", "");
+    PsiDocumentManager.getInstance(myFixture.getProject()).commitAllDocuments();
+    final String fileName = getTestName(false) + extension;
+    myFixture.configureByFile(fileName);
+    myFixture.enableInspections(inspectionClass);
+    myFixture.checkHighlighting(true, false, true);
+  }
+
+  private void doPyTest(@NotNull Class<? extends LocalInspectionTool> inspectionClass) {
+    doTest(inspectionClass, ".py");
+  }
+
+  private void doPyiTest(@NotNull Class<? extends LocalInspectionTool> inspectionClass) {
+    doTest(inspectionClass, ".pyi");
+  }
+
+  public void testUnresolvedModuleAttributes() {
+    doPyTest(PyUnresolvedReferencesInspection.class);
+  }
+
+  public void testUnresolvedClassAttributes() {
+    doPyTest(PyUnresolvedReferencesInspection.class);
+  }
+
+  public void testOverloads() {
+    doPyTest(PyTypeCheckerInspection.class);
+  }
+
+  public void testPyiUnusedParameters() {
+    doPyiTest(PyUnusedLocalInspection.class);
+  }
+
+  public void testPyiStatementEffect() {
+    doPyiTest(PyStatementEffectInspection.class);
+  }
+}
diff --git a/python/testSrc/com/jetbrains/python/pyi/PyiParsingTest.java b/python/testSrc/com/jetbrains/python/pyi/PyiParsingTest.java
new file mode 100644 (file)
index 0000000..269d008
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.testFramework.ParsingTestCase;
+import com.intellij.testFramework.TestDataPath;
+import com.jetbrains.python.PythonDialectsTokenSetContributor;
+import com.jetbrains.python.PythonTestUtil;
+import com.jetbrains.python.PythonTokenSetContributor;
+import com.jetbrains.python.psi.LanguageLevel;
+
+/**
+ * @author vlan
+ */
+@TestDataPath("$CONTENT_ROOT/../testData/pyi/parsing")
+public class PyiParsingTest extends ParsingTestCase {
+  public PyiParsingTest() {
+    super("pyi/parsing", "pyi", new PyiParserDefinition());
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    registerExtensionPoint(PythonDialectsTokenSetContributor.EP_NAME, PythonDialectsTokenSetContributor.class);
+    registerExtension(PythonDialectsTokenSetContributor.EP_NAME, new PythonTokenSetContributor());
+  }
+
+  @Override
+  protected String getTestDataPath() {
+    return PythonTestUtil.getTestDataPath();
+  }
+
+  private void doTest() {
+    doTest(true);
+  }
+
+  public void testSimple() {
+    doTest();
+    assertInstanceOf(myFile, PyiFile.class);
+    final PyiFile pyiFile = (PyiFile)myFile;
+    assertEquals(LanguageLevel.PYTHON35, pyiFile.getLanguageLevel());
+  }
+}
diff --git a/python/testSrc/com/jetbrains/python/pyi/PyiResolveTest.java b/python/testSrc/com/jetbrains/python/pyi/PyiResolveTest.java
new file mode 100644 (file)
index 0000000..2c0b08f
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.jetbrains.python.PythonTestUtil;
+import com.jetbrains.python.fixtures.PyMultiFileResolveTestCase;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyTargetExpression;
+
+/**
+ * @author vlan
+ */
+public class PyiResolveTest extends PyMultiFileResolveTestCase {
+  @Override
+  protected String getTestDataPath() {
+    return PythonTestUtil.getTestDataPath() + "/pyi/resolve";
+  }
+
+  public void testClassInsidePyiFile() {
+    assertResolvesTo(PyClass.class, "C");
+  }
+
+  public void testBuiltinInt() {
+    assertResolvesTo(PyClass.class, "int");
+  }
+
+  public void testFromPyiToClassInPy() {
+    assertResolvesTo(PyClass.class, "C");
+  }
+
+  public void testModuleAttribute() {
+    assertResolvesTo(PyTargetExpression.class, "foo");
+  }
+}
diff --git a/python/testSrc/com/jetbrains/python/pyi/PyiTypeTest.java b/python/testSrc/com/jetbrains/python/pyi/PyiTypeTest.java
new file mode 100644 (file)
index 0000000..cb9e6a0
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * 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.pyi;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.testFramework.LightProjectDescriptor;
+import com.jetbrains.python.documentation.PythonDocumentationProvider;
+import com.jetbrains.python.fixtures.PyLightProjectDescriptor;
+import com.jetbrains.python.fixtures.PyTestCase;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.psi.PyTypedElement;
+import com.jetbrains.python.psi.types.PyType;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author vlan
+ */
+public class PyiTypeTest extends PyTestCase {
+  @Nullable
+  @Override
+  protected LightProjectDescriptor getProjectDescriptor() {
+    return new PyLightProjectDescriptor(PYTHON_3_MOCK_SDK) {
+      @Override
+      public void configureModule(Module module, ModifiableRootModel model, ContentEntry contentEntry) {
+        createLibrary(model, "pyiStubs", "/community/python/testData/pyi/pyiStubs");
+      }
+    };
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    setLanguageLevel(LanguageLevel.PYTHON35);
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    setLanguageLevel(null);
+    super.tearDown();
+  }
+
+  private void doTest(@NotNull String expectedType) {
+    myFixture.copyDirectoryToProject("pyi/type/" + getTestName(true), "");
+    myFixture.copyDirectoryToProject("typing", "");
+    PsiDocumentManager.getInstance(myFixture.getProject()).commitAllDocuments();
+    final String fileName = getTestName(false) + ".py";
+    myFixture.configureByFile(fileName);
+    final PsiElement element = myFixture.getElementAtCaret();
+    assertNotNull("Could not find element at caret in: " + myFixture.getFile());
+    assertInstanceOf(element, PyTypedElement.class);
+    final PyTypedElement typedElement = (PyTypedElement)element;
+    final Project project = element.getProject();
+    final PsiFile containingFile = element.getContainingFile();
+    assertType(expectedType, typedElement, TypeEvalContext.codeAnalysis(project, containingFile));
+    assertType(expectedType, typedElement, TypeEvalContext.userInitiated(project, containingFile));
+  }
+
+  private static void assertType(@NotNull String expectedType, @NotNull PyTypedElement element, @NotNull TypeEvalContext context) {
+    final PyType actual = context.getType(element);
+    final String actualType = PythonDocumentationProvider.getTypeName(actual, context);
+    assertEquals("Failed in " + context + " context", expectedType, actualType);
+  }
+
+  public void testFunctionParameter() {
+    doTest("int");
+  }
+
+  public void testFunctionReturnType() {
+    doTest("Optional[int]");
+  }
+
+  public void testFunctionType() {
+    doTest("(x: int) -> dict");
+  }
+
+  public void testModuleAttribute() {
+    doTest("int");
+  }
+
+  public void testPyiOnPythonPath() {
+    doTest("int");
+  }
+
+  public void testOverloadedReturnType() {
+    doTest("str");
+  }
+}