PY-20377 Python dialect for annotations inside function type comments
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Sun, 24 Jul 2016 10:57:49 +0000 (12:57 +0200)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Mon, 24 Oct 2016 21:03:49 +0000 (00:03 +0300)
I also get rid of the legacy support of function type comments:
namely, removed PyFunctionTypeCommentReferenceContributor, and
PyTypingAnnotationInjector now handles type comments both for functions
and regular target expressions.

33 files changed:
python/src/META-INF/python-core-common.xml
python/src/com/jetbrains/python/codeInsight/PyFunctionTypeCommentReferenceContributor.java [deleted file]
python/src/com/jetbrains/python/codeInsight/PyTypingAnnotationInjector.java
python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationDialect.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationElementTypes.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationFileElementType.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationFileType.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationParser.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationParserDefinition.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyFunctionTypeAnnotation.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyFunctionTypeAnnotationFile.java [new file with mode: 0644]
python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyParameterTypeList.java [new file with mode: 0644]
python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java
python/src/com/jetbrains/python/validation/StarAnnotator.java
python/testData/functionTypeComment/parsing/DanglingComma.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/DefAsFirstType.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/Ellipsis.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/Empty.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/EmptyFunctionType.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/LambdaAsFirstType.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/NoArrowAndReturnType.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/NoClosingParenthesis.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/NoReturnType.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/NoTypeAfterStar.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/Simple.txt [new file with mode: 0644]
python/testData/functionTypeComment/parsing/Varargs.txt [new file with mode: 0644]
python/testData/resolve/FunctionTypeComment.py [deleted file]
python/testData/resolve/FunctionTypeCommentParamTypeReference.py [new file with mode: 0644]
python/testData/resolve/FunctionTypeCommentReturnTypeReference.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PyFunctionTypeAnnotationParsingTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PyInjectionResolveTest.java
python/testSrc/com/jetbrains/python/PyResolveTest.java
python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java

index 18e470efbdc5fb5e3215503c148f261358bedfee..9d48ebc64c77463ca59d166560113a575317295a 100644 (file)
                    serviceImplementation="com.jetbrains.python.documentation.PyDocumentationSettings"/>
     <psi.referenceContributor implementation="com.jetbrains.python.documentation.docstrings.DocStringReferenceContributor"/>
     <psi.referenceContributor implementation="com.jetbrains.python.codeInsight.PythonFormattedStringReferenceContributor"/>
-    <psi.referenceContributor implementation="com.jetbrains.python.codeInsight.PyFunctionTypeCommentReferenceContributor"/>
     <completion.contributor language="Python" implementationClass="com.jetbrains.python.documentation.docstrings.DocStringTagCompletionContributor"/>
     <completion.contributor language="Python" implementationClass="com.jetbrains.python.documentation.docstrings.DocStringSectionHeaderCompletionContributor"/>
 
     <lang.parserDefinition language="PyDocstring" implementationClass="com.jetbrains.python.documentation.doctest.PyDocstringParserDefinition"/>
     <highlightErrorFilter implementation="com.jetbrains.python.documentation.doctest.PyDocstringErrorFilter"/>
 
+    <!-- PyFunctionTypeAnnotation -->
+    <lang.parserDefinition language="PyFunctionTypeComment" 
+                           implementationClass="com.jetbrains.python.codeInsight.functionTypeComments.PyFunctionTypeAnnotationParserDefinition"/>
+
     <!-- Packaging -->
     <moduleService serviceInterface="com.jetbrains.python.packaging.PyPackageRequirementsSettings"
                    serviceImplementation="com.jetbrains.python.packaging.PyPackageRequirementsSettings"/>
diff --git a/python/src/com/jetbrains/python/codeInsight/PyFunctionTypeCommentReferenceContributor.java b/python/src/com/jetbrains/python/codeInsight/PyFunctionTypeCommentReferenceContributor.java
deleted file mode 100644 (file)
index 8fa3f7e..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2000-2016 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.codeInsight;
-
-import com.intellij.openapi.util.TextRange;
-import com.intellij.patterns.PatternCondition;
-import com.intellij.patterns.PsiElementPattern;
-import com.intellij.psi.*;
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.util.ArrayUtil;
-import com.intellij.util.ProcessingContext;
-import com.jetbrains.python.documentation.docstrings.DocStringTypeReference;
-import com.jetbrains.python.psi.PyFunction;
-import com.jetbrains.python.psi.PyImportElement;
-import com.jetbrains.python.psi.PyStatementList;
-import com.jetbrains.python.psi.types.PyType;
-import com.jetbrains.python.psi.types.PyTypeParser;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * @author Mikhail Golubev
- */
-public class PyFunctionTypeCommentReferenceContributor extends PsiReferenceContributor {
-  public static final PsiElementPattern.Capture<PsiComment> TYPE_COMMENT_PATTERN = psiElement(PsiComment.class)
-    .withParent(psiElement(PyStatementList.class).withParent(PyFunction.class))
-    .with(new PatternCondition<PsiComment>("isPep484TypeComment") {
-      @Override
-      public boolean accepts(@NotNull PsiComment comment, ProcessingContext context) {
-        final PyFunction func = PsiTreeUtil.getParentOfType(comment, PyFunction.class);
-        return func != null && func.getTypeComment() == comment;
-      }
-    });
-  
-
-  @Override
-  public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
-    registrar.registerReferenceProvider(TYPE_COMMENT_PATTERN, new PsiReferenceProvider() {
-      @NotNull
-      @Override
-      public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
-        final PsiComment comment = (PsiComment)element;
-        final String wholeText = comment.getText();
-        final String typeText = PyTypingTypeProvider.getTypeCommentValue(wholeText);
-        if (typeText != null) {
-          final int prefixLength = wholeText.length() - typeText.length();
-          final List<PsiReference> references = parseTypeReferences(element, typeText, prefixLength);
-          return ArrayUtil.toObjectArray(references, PsiReference.class);
-        }
-        return PsiReference.EMPTY_ARRAY;
-      }
-    });
-  }
-  
-  @SuppressWarnings("Duplicates")
-  @NotNull
-  private static List<PsiReference> parseTypeReferences(@NotNull PsiElement anchor, @NotNull String typeText, int offsetInComment) {
-    final List<PsiReference> result = new ArrayList<>();
-    final PyTypeParser.ParseResult parseResult = PyTypeParser.parsePep484FunctionTypeComment(anchor, typeText);
-    final Map<TextRange, ? extends PyType> types = parseResult.getTypes();
-    final Map<? extends PyType, TextRange> fullRanges = parseResult.getFullRanges();
-    for (Map.Entry<TextRange, ? extends PyType> pair : types.entrySet()) {
-      final PyType t = pair.getValue();
-      final TextRange range = pair.getKey().shiftRight(offsetInComment);
-      final TextRange fullRange = fullRanges.containsKey(t) ? fullRanges.get(t).shiftRight(offsetInComment) : range;
-      final PyImportElement importElement = parseResult.getImports().get(t);
-      result.add(new DocStringTypeReference(anchor, range, fullRange, t, importElement));
-    }
-    return result;
-  }
-}
index 884e28f78e15fe86d4076065074946c7225c0813..934b09d894744c67f4503d4c68c5802b8ebf6381 100644 (file)
@@ -23,8 +23,10 @@ import com.intellij.psi.PsiComment;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiLanguageInjectionHost;
 import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.codeInsight.functionTypeComments.PyFunctionTypeAnnotationDialect;
 import com.jetbrains.python.documentation.doctest.PyDocstringLanguageDialect;
 import com.jetbrains.python.psi.PyAnnotation;
+import com.jetbrains.python.psi.PyFunction;
 import com.jetbrains.python.psi.PyStringLiteralExpression;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -33,6 +35,9 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
+ * Injects fragments for type annotations either in string literals (quoted annotations containing forward references) or
+ * in type comments starting with <tt># type:</tt>.
+ *
  * @author vlan
  */
 public class PyTypingAnnotationInjector extends PyInjectorBase {
@@ -66,21 +71,34 @@ public class PyTypingAnnotationInjector extends PyInjectorBase {
     final Matcher m = PyTypingTypeProvider.TYPE_COMMENT_PATTERN.matcher(text);
     if (m.matches()) {
       final String annotationText = m.group(1);
-      if (annotationText != null && isTypingAnnotation(annotationText)) {
+      if (annotationText != null) {
         final int start = m.start(1);
         final int end = m.end(1);
         if (start < end && allowInjectionInComment(host)) {
-          final Language language = PyDocstringLanguageDialect.getInstance();
-          registrar.startInjecting(language);
-          registrar.addPlace("", "", host, TextRange.create(start, end));
-          registrar.doneInjecting();
-          return new PyInjectionUtil.InjectionResult(true, true);
+          Language language = null;
+          if (isFunctionTypeComment(host)) {
+            language = PyFunctionTypeAnnotationDialect.INSTANCE;
+          }
+          else if (isTypingAnnotation(annotationText)) {
+            language = PyDocstringLanguageDialect.getInstance();
+          }
+          if (language != null) {
+            registrar.startInjecting(language);
+            registrar.addPlace("", "", host, TextRange.create(start, end));
+            registrar.doneInjecting();
+            return new PyInjectionUtil.InjectionResult(true, true);
+          }
         }
       }
     }
     return PyInjectionUtil.InjectionResult.EMPTY;
   }
 
+  private static boolean isFunctionTypeComment(@NotNull PsiElement comment) {
+   final PyFunction function = PsiTreeUtil.getParentOfType(comment, PyFunction.class);
+    return function != null && function.getTypeComment() == comment;
+  }
+
   private static boolean isTypingAnnotation(@NotNull String s) {
     return RE_TYPING_ANNOTATION.matcher(s).matches();
   }
@@ -88,12 +106,6 @@ public class PyTypingAnnotationInjector extends PyInjectorBase {
   private static boolean allowInjectionInComment(@NotNull PsiLanguageInjectionHost host) {
     // XXX: Don't inject PyDocstringLanguage during completion inside comments due to an exception related to finding ShredImpl's
     // hostElementPointer
-    if (CompletionUtil.getOriginalOrSelf(host) != host) {
-      return false;
-    }
-    if (PyFunctionTypeCommentReferenceContributor.TYPE_COMMENT_PATTERN.accepts(host)) {
-      return false;
-    }
-    return true;
+    return CompletionUtil.getOriginalOrSelf(host) == host;
   }
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationDialect.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationDialect.java
new file mode 100644 (file)
index 0000000..c66c0f1
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments;
+
+import com.intellij.lang.InjectableLanguage;
+import com.intellij.lang.Language;
+import com.jetbrains.python.PythonLanguage;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotationDialect extends Language implements InjectableLanguage {
+  public static final PyFunctionTypeAnnotationDialect INSTANCE = new PyFunctionTypeAnnotationDialect();
+
+  protected PyFunctionTypeAnnotationDialect() {
+    super(PythonLanguage.getInstance(), "PyFunctionTypeComment");
+  }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationElementTypes.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationElementTypes.java
new file mode 100644 (file)
index 0000000..124adeb
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments;
+
+import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyFunctionTypeAnnotation;
+import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyParameterTypeList;
+import com.jetbrains.python.psi.PyElementType;
+
+/**
+ * @author Mikhail Golubev
+ */
+public interface PyFunctionTypeAnnotationElementTypes {
+  PyElementType FUNCTION_SIGNATURE = new PyElementType("FUNCTION_SIGNATURE", PyFunctionTypeAnnotation.class);
+  PyElementType PARAMETER_TYPE_LIST = new PyElementType("PARAMETER_TYPE_LIST", PyParameterTypeList.class);
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationFileElementType.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationFileElementType.java
new file mode 100644 (file)
index 0000000..0853fdb
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments;
+
+import com.jetbrains.python.psi.PyFileElementType;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotationFileElementType extends PyFileElementType {
+  public static final PyFunctionTypeAnnotationFileElementType INSTANCE =
+    new PyFunctionTypeAnnotationFileElementType(PyFunctionTypeAnnotationDialect.INSTANCE);
+
+  public PyFunctionTypeAnnotationFileElementType(PyFunctionTypeAnnotationDialect instance) {
+    super(instance);
+  }
+
+  @NotNull
+  @Override
+  public String getExternalId() {
+    return "PyFunctionTypeComment.ID";
+  }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationFileType.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationFileType.java
new file mode 100644 (file)
index 0000000..debfdea
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments;
+
+import com.jetbrains.python.PythonFileType;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotationFileType extends PythonFileType {
+  public static final PyFunctionTypeAnnotationFileType INSTANCE = new PyFunctionTypeAnnotationFileType();
+
+  public PyFunctionTypeAnnotationFileType() {
+    super(PyFunctionTypeAnnotationDialect.INSTANCE);
+  }
+
+  @NotNull
+  @Override
+  public String getName() {
+    return "PythonFunctionTypeComment";
+  }
+
+  @NotNull
+  @Override
+  public String getDescription() {
+    return "Python PEP-484 function type comment";
+  }
+
+  @NotNull
+  @Override
+  public String getDefaultExtension() {
+    return "functionTypeComment";
+  }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationParser.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationParser.java
new file mode 100644 (file)
index 0000000..9c6fdc4
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments;
+
+import com.intellij.lang.PsiBuilder;
+import com.intellij.psi.tree.IElementType;
+import com.jetbrains.python.PyElementTypes;
+import com.jetbrains.python.PyTokenTypes;
+import com.jetbrains.python.documentation.doctest.PyDocstringTokenTypes;
+import com.jetbrains.python.parsing.ExpressionParsing;
+import com.jetbrains.python.parsing.ParsingContext;
+import com.jetbrains.python.parsing.PyParser;
+import com.jetbrains.python.parsing.StatementParsing;
+import com.jetbrains.python.psi.LanguageLevel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotationParser extends PyParser {
+  @Override
+  protected ParsingContext createParsingContext(PsiBuilder builder, LanguageLevel languageLevel, StatementParsing.FUTURE futureFlag) {
+    return new ParsingContext(builder, languageLevel, futureFlag) {
+      private StatementParsing myStatementParsing = new AnnotationParser(this, futureFlag);
+      private ExpressionParsing myExpressionParsing = new ExpressionParsing(this) {
+        @Override
+        protected IElementType getReferenceType() {
+          return PyDocstringTokenTypes.DOC_REFERENCE;
+        }
+      };
+
+      @Override
+      public StatementParsing getStatementParser() {
+        return myStatementParsing;
+      }
+
+      @Override
+      public ExpressionParsing getExpressionParser() {
+        return myExpressionParsing;
+      }
+    };
+  }
+
+  private static class AnnotationParser extends StatementParsing {
+    public AnnotationParser(ParsingContext context, @Nullable FUTURE futureFlag) {
+      super(context, futureFlag);
+    }
+
+    @Override
+    public void parseStatement() {
+      if (myBuilder.eof()) return;
+      parseFunctionType();
+    }
+
+    private void parseFunctionType() {
+      if (atToken(PyTokenTypes.LPAR)) {
+        final PsiBuilder.Marker funcTypeMark = myBuilder.mark();
+        parseParameterTypeList();
+        checkMatches(PyTokenTypes.RARROW, "'->' expected");
+        final boolean parsed = getExpressionParser().parseSingleExpression(false);
+        if (!parsed) {
+          myBuilder.error("expression expected");
+        }
+        funcTypeMark.done(PyFunctionTypeAnnotationElementTypes.FUNCTION_SIGNATURE);
+      }
+      recoverUntilMatches("unexpected tokens");
+    }
+
+    private void parseParameterTypeList() {
+      assert atToken(PyTokenTypes.LPAR);
+      final PsiBuilder.Marker listMark = myBuilder.mark();
+      myBuilder.advanceLexer();
+      
+      final ExpressionParsing exprParser = getExpressionParser();
+      int paramCount = 0;
+      while (!(atAnyOfTokens(PyTokenTypes.RPAR, PyTokenTypes.RARROW, PyTokenTypes.STATEMENT_BREAK) || myBuilder.eof()) ) {
+        if (paramCount > 0) {
+          checkMatches(PyTokenTypes.COMMA, "',' expected");
+        }
+        boolean parsed;
+        if (atAnyOfTokens(PyTokenTypes.MULT, PyTokenTypes.EXP)) {
+          final PsiBuilder.Marker starredExprMarker = myBuilder.mark();
+          myBuilder.advanceLexer();
+          parsed = exprParser.parseSingleExpression(false);
+          starredExprMarker.done(PyElementTypes.STAR_EXPRESSION);
+        }
+        else {
+          parsed = exprParser.parseSingleExpression(false);
+        }
+        if (!parsed) {
+          myBuilder.error("expression expected");
+          recoverUntilMatches("expression expected", PyTokenTypes.COMMA, PyTokenTypes.RPAR, PyTokenTypes.RARROW, PyTokenTypes.STATEMENT_BREAK);
+        }
+        paramCount++;
+      }
+      checkMatches(PyTokenTypes.RPAR, "')' expected");
+      listMark.done(PyFunctionTypeAnnotationElementTypes.PARAMETER_TYPE_LIST);
+    }
+
+    private void recoverUntilMatches(@NotNull String errorMessage, @NotNull IElementType... types) {
+      final PsiBuilder.Marker errorMarker = myBuilder.mark();
+      boolean hasNonWhitespaceTokens = false;
+      while (!(atAnyOfTokens(types) || myBuilder.eof())) {
+        // Regular whitespace tokens are already skipped by advancedLexer() 
+        if (!atToken(PyTokenTypes.STATEMENT_BREAK)) {
+          hasNonWhitespaceTokens = true;
+        }
+        myBuilder.advanceLexer();
+      }
+      if (hasNonWhitespaceTokens) {
+        errorMarker.error(errorMessage);
+      }
+      else {
+        errorMarker.drop();
+      }
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationParserDefinition.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/PyFunctionTypeAnnotationParserDefinition.java
new file mode 100644 (file)
index 0000000..6349678
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments;
+
+import com.intellij.lang.PsiParser;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.tree.IFileElementType;
+import com.intellij.psi.tree.TokenSet;
+import com.jetbrains.python.PythonParserDefinition;
+import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyFunctionTypeAnnotationFile;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotationParserDefinition extends PythonParserDefinition {
+
+  @NotNull
+  @Override
+  public TokenSet getCommentTokens() {
+    return TokenSet.EMPTY;
+  }
+
+  @Override
+  public PsiFile createFile(FileViewProvider viewProvider) {
+    return new PyFunctionTypeAnnotationFile(viewProvider);
+  }
+
+  @Override
+  public IFileElementType getFileNodeType() {
+    return PyFunctionTypeAnnotationFileElementType.INSTANCE;
+  }
+
+  @NotNull
+  @Override
+  public PsiParser createParser(Project project) {
+    return new PyFunctionTypeAnnotationParser();
+  }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyFunctionTypeAnnotation.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyFunctionTypeAnnotation.java
new file mode 100644 (file)
index 0000000..db5907c
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments.psi;
+
+import com.intellij.lang.ASTNode;
+import com.jetbrains.python.psi.PyExpression;
+import com.jetbrains.python.psi.impl.PyElementImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotation extends PyElementImpl {
+  public PyFunctionTypeAnnotation(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @NotNull
+  public PyParameterTypeList getParameterTypeList() {
+    return findNotNullChildByClass(PyParameterTypeList.class);
+  }  
+  
+  @Nullable
+  public PyExpression getReturnType() {
+    return findChildByClass(PyExpression.class);
+  }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyFunctionTypeAnnotationFile.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyFunctionTypeAnnotationFile.java
new file mode 100644 (file)
index 0000000..3dc2f09
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments.psi;
+
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.psi.FileViewProvider;
+import com.jetbrains.python.codeInsight.functionTypeComments.PyFunctionTypeAnnotationDialect;
+import com.jetbrains.python.codeInsight.functionTypeComments.PyFunctionTypeAnnotationFileType;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.psi.impl.PyFileImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotationFile extends PyFileImpl {
+
+  public PyFunctionTypeAnnotationFile(FileViewProvider viewProvider) {
+    super(viewProvider, PyFunctionTypeAnnotationDialect.INSTANCE);
+  }
+
+  @NotNull
+  @Override
+  public FileType getFileType() {
+    return PyFunctionTypeAnnotationFileType.INSTANCE;
+  }
+
+  @Override
+  public String toString() {
+    return "FunctionTypeComment:" + getName();
+  }
+
+  @Override
+  public LanguageLevel getLanguageLevel() {
+    // The same as for .pyi files
+    return LanguageLevel.PYTHON35;
+  }
+
+  @Nullable
+  public PyFunctionTypeAnnotation getAnnotation() {
+    return findChildByClass(PyFunctionTypeAnnotation.class);
+  }
+}
+
diff --git a/python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyParameterTypeList.java b/python/src/com/jetbrains/python/codeInsight/functionTypeComments/psi/PyParameterTypeList.java
new file mode 100644 (file)
index 0000000..0522e2f
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2016 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.codeInsight.functionTypeComments.psi;
+
+import com.intellij.lang.ASTNode;
+import com.jetbrains.python.PythonDialectsTokenSetProvider;
+import com.jetbrains.python.psi.PyExpression;
+import com.jetbrains.python.psi.impl.PyElementImpl;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyParameterTypeList extends PyElementImpl {
+  public PyParameterTypeList(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @NotNull
+  public List<PyExpression> getParameterTypes() {
+    return findChildrenByType(PythonDialectsTokenSetProvider.INSTANCE.getExpressionTokens());
+  }
+}
index f7261c8cd3b4d31f24aed884f30cd537f9992611..45a396a354a05ed13bf5b79527284bf5297b2951 100644 (file)
@@ -41,7 +41,6 @@ import com.jetbrains.python.PyCustomType;
 import com.jetbrains.python.PyNames;
 import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
 import com.jetbrains.python.codeInsight.PyCustomMember;
-import com.jetbrains.python.codeInsight.PyFunctionTypeCommentReferenceContributor;
 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
 import com.jetbrains.python.codeInsight.imports.AutoImportHintAction;
@@ -293,13 +292,6 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
       if (comment instanceof PsiLanguageInjectionHost) {
         processInjection((PsiLanguageInjectionHost)comment);
       }
-      if (PyFunctionTypeCommentReferenceContributor.TYPE_COMMENT_PATTERN.accepts(comment)) {
-        for (PsiReference reference : comment.getReferences()) {
-          if (reference instanceof PsiPolyVariantReference) {
-            markTargetImportsAsUsed((PsiPolyVariantReference)reference);
-          }
-        }
-      }
     }
 
     @Override
index 1a0e01b415e13bc608d5de50a7eb0a38af217dce..55cda35fadc9c1d893e984461f1515deeb1dc32d 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.jetbrains.python.validation;
 
+import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyParameterTypeList;
 import com.jetbrains.python.psi.PyStarExpression;
 
 /**
@@ -24,7 +25,7 @@ public class StarAnnotator extends PyAnnotator {
   @Override
   public void visitPyStarExpression(PyStarExpression node) {
     super.visitPyStarExpression(node);
-    if (!node.isAssignmentTarget() && !node.isUnpacking()) {
+    if (!node.isAssignmentTarget() && !node.isUnpacking() && !(node.getParent() instanceof PyParameterTypeList)) {
       getHolder().createErrorAnnotation(node, "Can't use starred expression here");
     }
   }
diff --git a/python/testData/functionTypeComment/parsing/DanglingComma.txt b/python/testData/functionTypeComment/parsing/DanglingComma.txt
new file mode 100644 (file)
index 0000000..16cf1ec
--- /dev/null
@@ -0,0 +1,15 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyReferenceExpression: int
+        PsiElement(Py:IDENTIFIER)('int')
+      PsiElement(Py:COMMA)(',')
+      PsiErrorElement:expression expected
+        <empty list>
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:RARROW)('->')
+    PsiWhiteSpace(' ')
+    PyNoneLiteralExpression
+      PsiElement(Py:NONE_KEYWORD)('None')
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/DefAsFirstType.txt b/python/testData/functionTypeComment/parsing/DefAsFirstType.txt
new file mode 100644 (file)
index 0000000..be8ad74
--- /dev/null
@@ -0,0 +1,11 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PsiErrorElement:')' expected
+        <empty list>
+  PsiErrorElement:unexpected tokens
+    PsiElement(Py:DEF_KEYWORD)('def')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:IDENTIFIER)('foo')
+    PsiElement(Py:RPAR)(')')
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/Ellipsis.txt b/python/testData/functionTypeComment/parsing/Ellipsis.txt
new file mode 100644 (file)
index 0000000..216c44d
--- /dev/null
@@ -0,0 +1,14 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyNoneLiteralExpression
+        PsiElement(Py:DOT)('.')
+        PsiElement(Py:DOT)('.')
+        PsiElement(Py:DOT)('.')
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:RARROW)('->')
+    PsiWhiteSpace(' ')
+    PyNoneLiteralExpression
+      PsiElement(Py:NONE_KEYWORD)('None')
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/Empty.txt b/python/testData/functionTypeComment/parsing/Empty.txt
new file mode 100644 (file)
index 0000000..dc892eb
--- /dev/null
@@ -0,0 +1,10 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:RARROW)('->')
+    PsiWhiteSpace(' ')
+    PyNoneLiteralExpression
+      PsiElement(Py:NONE_KEYWORD)('None')
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/EmptyFunctionType.txt b/python/testData/functionTypeComment/parsing/EmptyFunctionType.txt
new file mode 100644 (file)
index 0000000..8713a3b
--- /dev/null
@@ -0,0 +1,2 @@
+FunctionTypeComment:a.functionTypeComment
+  <empty list>
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/LambdaAsFirstType.txt b/python/testData/functionTypeComment/parsing/LambdaAsFirstType.txt
new file mode 100644 (file)
index 0000000..a7e7210
--- /dev/null
@@ -0,0 +1,14 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyLambdaExpression
+        PsiElement(Py:LAMBDA_KEYWORD)('lambda')
+        PyParameterList
+          <empty list>
+        PsiElement(Py:COLON)(':')
+        PsiWhiteSpace(' ')
+        PyNumericLiteralExpression
+          PsiElement(Py:INTEGER_LITERAL)('42')
+      PsiErrorElement:')' expected
+        <empty list>
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/NoArrowAndReturnType.txt b/python/testData/functionTypeComment/parsing/NoArrowAndReturnType.txt
new file mode 100644 (file)
index 0000000..3911373
--- /dev/null
@@ -0,0 +1,9 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyReferenceExpression: int
+        PsiElement(Py:IDENTIFIER)('int')
+      PsiElement(Py:RPAR)(')')
+    PsiErrorElement:'->' expected
+      <empty list>
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/NoClosingParenthesis.txt b/python/testData/functionTypeComment/parsing/NoClosingParenthesis.txt
new file mode 100644 (file)
index 0000000..22b8bd3
--- /dev/null
@@ -0,0 +1,8 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyReferenceExpression: int
+        PsiElement(Py:IDENTIFIER)('int')
+      PsiErrorElement:')' expected
+        <empty list>
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/NoReturnType.txt b/python/testData/functionTypeComment/parsing/NoReturnType.txt
new file mode 100644 (file)
index 0000000..42faa91
--- /dev/null
@@ -0,0 +1,12 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyReferenceExpression: int
+        PsiElement(Py:IDENTIFIER)('int')
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:RARROW)('->')
+    PsiErrorElement:expression expected
+      <empty list>
+  PsiWhiteSpace(' ')
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/NoTypeAfterStar.txt b/python/testData/functionTypeComment/parsing/NoTypeAfterStar.txt
new file mode 100644 (file)
index 0000000..ca16a0b
--- /dev/null
@@ -0,0 +1,14 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyStarExpression
+        PsiElement(Py:MULT)('*')
+      PsiErrorElement:expression expected
+        <empty list>
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:RARROW)('->')
+    PsiWhiteSpace(' ')
+    PyReferenceExpression: int
+      PsiElement(Py:IDENTIFIER)('int')
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/Simple.txt b/python/testData/functionTypeComment/parsing/Simple.txt
new file mode 100644 (file)
index 0000000..0f0976a
--- /dev/null
@@ -0,0 +1,16 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyReferenceExpression: int
+        PsiElement(Py:IDENTIFIER)('int')
+      PsiElement(Py:COMMA)(',')
+      PsiWhiteSpace(' ')
+      PyReferenceExpression: str
+        PsiElement(Py:IDENTIFIER)('str')
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:RARROW)('->')
+    PsiWhiteSpace(' ')
+    PyNoneLiteralExpression
+      PsiElement(Py:NONE_KEYWORD)('None')
\ No newline at end of file
diff --git a/python/testData/functionTypeComment/parsing/Varargs.txt b/python/testData/functionTypeComment/parsing/Varargs.txt
new file mode 100644 (file)
index 0000000..26da121
--- /dev/null
@@ -0,0 +1,20 @@
+FunctionTypeComment:a.functionTypeComment
+  PyFunctionTypeAnnotation
+    PyParameterTypeList
+      PsiElement(Py:LPAR)('(')
+      PyStarExpression
+        PsiElement(Py:MULT)('*')
+        PyReferenceExpression: int
+          PsiElement(Py:IDENTIFIER)('int')
+      PsiElement(Py:COMMA)(',')
+      PsiWhiteSpace(' ')
+      PyStarExpression
+        PsiElement(Py:EXP)('**')
+        PyReferenceExpression: str
+          PsiElement(Py:IDENTIFIER)('str')
+      PsiElement(Py:RPAR)(')')
+    PsiWhiteSpace(' ')
+    PsiElement(Py:RARROW)('->')
+    PsiWhiteSpace(' ')
+    PyNoneLiteralExpression
+      PsiElement(Py:NONE_KEYWORD)('None')
\ No newline at end of file
diff --git a/python/testData/resolve/FunctionTypeComment.py b/python/testData/resolve/FunctionTypeComment.py
deleted file mode 100644 (file)
index 960face..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-class MyClass:
-    pass
-
-def f(*args):
-    # type: (*MyClass) -> None
-               <ref>
-    pass
\ No newline at end of file
diff --git a/python/testData/resolve/FunctionTypeCommentParamTypeReference.py b/python/testData/resolve/FunctionTypeCommentParamTypeReference.py
new file mode 100644 (file)
index 0000000..878b80e
--- /dev/null
@@ -0,0 +1,8 @@
+class MyClass:
+    pass
+
+
+def f(x):
+    # type: (MyClass) -> Any
+              <ref>
+    pass
\ No newline at end of file
diff --git a/python/testData/resolve/FunctionTypeCommentReturnTypeReference.py b/python/testData/resolve/FunctionTypeCommentReturnTypeReference.py
new file mode 100644 (file)
index 0000000..40d5fa1
--- /dev/null
@@ -0,0 +1,8 @@
+class MyClass:
+    pass
+
+
+def f(x):
+    # type: (Any) -> MyClass
+                      <ref>
+    pass
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/PyFunctionTypeAnnotationParsingTest.java b/python/testSrc/com/jetbrains/python/PyFunctionTypeAnnotationParsingTest.java
new file mode 100644 (file)
index 0000000..84761ca
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2000-2016 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;
+
+import com.intellij.testFramework.ParsingTestCase;
+import com.jetbrains.python.codeInsight.functionTypeComments.PyFunctionTypeAnnotationParserDefinition;
+import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyFunctionTypeAnnotationFile;
+import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyFunctionTypeAnnotation;
+import com.jetbrains.python.documentation.doctest.PyDocstringTokenSetContributor;
+import com.jetbrains.python.psi.PyExpression;
+import com.jetbrains.python.psi.PyNoneLiteralExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyFunctionTypeAnnotationParsingTest extends ParsingTestCase {
+  public PyFunctionTypeAnnotationParsingTest() {
+    super("functionTypeComment/parsing", "functionTypeComment", new PyFunctionTypeAnnotationParserDefinition(), new PythonParserDefinition());
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    registerExtensionPoint(PythonDialectsTokenSetContributor.EP_NAME, PythonDialectsTokenSetContributor.class);
+    registerExtension(PythonDialectsTokenSetContributor.EP_NAME, new PythonTokenSetContributor());
+    registerExtension(PythonDialectsTokenSetContributor.EP_NAME, new PyDocstringTokenSetContributor());
+  }
+
+  protected void doCodeTest(@NotNull String typeAnnotation) {
+    try {
+      super.doCodeTest(typeAnnotation);
+    }
+    catch (IOException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  @Nullable
+  private PyFunctionTypeAnnotation getParsedAnnotation() {
+    final PyFunctionTypeAnnotationFile file = assertInstanceOf(myFile, PyFunctionTypeAnnotationFile.class);
+    return file.getAnnotation();
+  }
+
+  public void testEmpty() {
+    doCodeTest("() -> None");
+
+    final PyFunctionTypeAnnotation annotation = getParsedAnnotation();
+    assertNotNull(annotation);
+    assertEmpty(annotation.getParameterTypeList().getParameterTypes());
+    final PyExpression returnType = annotation.getReturnType();
+    assertNotNull(returnType);
+    assertEquals("None", returnType.getText());
+  }
+
+  public void testSimple() {
+    doCodeTest("(int, str) -> None");
+  }
+
+  public void testVarargs() {
+    doCodeTest("(*int, **str) -> None");
+  }
+
+  public void testEllipsis() {
+    doCodeTest("(...) -> None");
+    final PyFunctionTypeAnnotation annotation = getParsedAnnotation();
+    final List<PyExpression> paramTypes = annotation.getParameterTypeList().getParameterTypes();
+    assertSize(1, paramTypes);
+    assertInstanceOf(paramTypes.get(0), PyNoneLiteralExpression.class);
+    final PyExpression returnType = annotation.getReturnType();
+    assertNotNull(returnType);
+  }
+
+  public void testNoReturnType() {
+    doCodeTest("(int) -> ");
+    final PyFunctionTypeAnnotation annotation = getParsedAnnotation();
+    final List<PyExpression> paramTypes = annotation.getParameterTypeList().getParameterTypes();
+    assertSize(1, paramTypes);
+    assertNull(annotation.getReturnType());
+  }
+
+  public void testNoArrowAndReturnType() {
+    doCodeTest("(int)");
+  }
+
+  public void testNoClosingParenthesis() {
+    doCodeTest("(int");
+  }
+
+  public void testDanglingComma() {
+    doCodeTest("(int,) -> None");
+  }
+
+  public void testEmptyFunctionType() {
+    doCodeTest("");
+    assertNull(getParsedAnnotation());
+  }
+
+  public void testNoTypeAfterStar() {
+    doCodeTest("(*) -> int");
+  }
+
+  public void testLambdaAsFirstType() {
+    doCodeTest("(lambda: 42");
+  }
+
+  public void testDefAsFirstType() {
+    doCodeTest("(def foo)");
+  }
+
+  @Override
+  protected String getTestDataPath() {
+    return PythonTestUtil.getTestDataPath();
+  }
+}
index 606d0c9fcd9337697a14bf66778e5ece3b539365..d6690e4f671f689bb8f8cc43cf07e7277df5a11d 100644 (file)
@@ -83,4 +83,14 @@ public class PyInjectionResolveTest extends PyResolveTestCase {
   public void testQuotedTypeReferenceTopLevel() {
     assertResolvesTo(LanguageLevel.PYTHON30, PyClass.class, "MyClass");
   }
+
+  // PY-20377
+  public void testFunctionTypeCommentParamTypeReference() {
+    assertResolvesTo(PyClass.class, "MyClass");
+  }
+
+  // PY-20377
+  public void testFunctionTypeCommentReturnTypeReference() {
+    assertResolvesTo(PyClass.class, "MyClass");
+  }
 }
index ea002c2e840b47b04ad26bc37878e3f4d09ca0e9..41b772fe85eef02168d69fad47b191b5b3ef6c4a 100644 (file)
@@ -667,11 +667,6 @@ public class PyResolveTest extends PyResolveTestCase {
     assertEquals("kwg", ((PyStringLiteralExpression)target).getStringValue());
   }
 
-  // PY-18254
-  public void testFunctionTypeComment() {
-    assertResolvesTo(PyClass.class, "MyClass");
-  }  
-  
   public void testPercentStringKeyWordArgWithParentheses() {
     PsiElement target = resolve();
     assertTrue(target instanceof PyStringLiteralExpression);
index 197301c6811f999d666d355e2d8885c80a456bac..bf26bca98ab24d313236696a96fcf64262da5db5 100644 (file)
@@ -691,7 +691,7 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
   // PY-18521
   public void testFunctionTypeCommentUsesImportsFromTyping() {
     myFixture.copyDirectoryToProject("typing", "");
-    doTest();
+    runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTest);
   }
   
   // PY-19084
@@ -739,4 +739,4 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
   protected Class<? extends PyInspection> getInspectionClass() {
     return PyUnresolvedReferencesInspection.class;
   }
-}
+}
\ No newline at end of file