diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index 300a2b8d2294..cbf24055a454 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -132,6 +132,11 @@
Python
+
+ com.jetbrains.python.codeInsight.intentions.SpecifyTypeInPy3AnnotationsIntention
+ Python
+
+
com.jetbrains.python.codeInsight.intentions.TypeAssertionIntention
Python
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 9f3bc849bdd6..28b4afc4719d 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -185,6 +185,9 @@ INTN.doc.string.stub=Insert documentation string stub
#SpecifyTypeInDocstringIntention
INTN.specify.type=Specify type for reference in docstring
+#SpecifyTypeInPy3AnnotationsIntention
+INTN.specify.type.in.annotation=Specify type for reference using annotation
+
#TypeAssertionIntention
INTN.insert.assertion=Insert type assertion
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInPy3AnnotationsIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInPy3AnnotationsIntention.java
new file mode 100644
index 000000000000..c3ba5d921520
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInPy3AnnotationsIntention.java
@@ -0,0 +1,113 @@
+package com.jetbrains.python.codeInsight.intentions;
+
+import com.intellij.codeInsight.CodeInsightUtilBase;
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInsight.template.*;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.IncorrectOperationException;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.types.PyReturnTypeReference;
+import com.jetbrains.python.psi.types.PyType;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * User: ktisha
+ *
+ * Helps to specify type in annotations in python3
+ */
+public class SpecifyTypeInPy3AnnotationsIntention implements IntentionAction {
+
+ public SpecifyTypeInPy3AnnotationsIntention() {
+ }
+
+ @NotNull
+ public String getText() {
+ return PyBundle.message("INTN.specify.type.in.annotation");
+ }
+
+ @NotNull
+ public String getFamilyName() {
+ return PyBundle.message("INTN.specify.type.in.annotation");
+ }
+
+ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
+ if (!LanguageLevel.forElement(file).isPy3K()) return false;
+
+ PyExpression problemElement = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset() - 1),
+ PyNamedParameter.class);
+ if (problemElement == null)
+ problemElement = PsiTreeUtil.getTopmostParentOfType(file.findElementAt(editor.getCaretModel().getOffset() - 1),
+ PyQualifiedExpression.class);
+ if (problemElement == null) return false;
+ if (problemElement instanceof PyQualifiedExpression) {
+ final PyExpression qualifier = ((PyQualifiedExpression)problemElement).getQualifier();
+ if (qualifier != null && !qualifier.getText().equals(PyNames.CANONICAL_SELF)) {
+ problemElement = qualifier;
+ }
+ }
+ final PyType type = problemElement.getType(TypeEvalContext.fast());
+ if (type == null || type instanceof PyReturnTypeReference) {
+ PyFunction pyFunction = PsiTreeUtil.getParentOfType(problemElement, PyFunction.class);
+ PsiReference reference = problemElement.getReference();
+ if (problemElement instanceof PyQualifiedExpression) {
+ final PyExpression qualifier = ((PyQualifiedExpression)problemElement).getQualifier();
+ if (qualifier != null && !qualifier.getText().equals(PyNames.CANONICAL_SELF)) reference = qualifier.getReference();
+ }
+ if (pyFunction != null && (problemElement instanceof PyParameter || reference != null && reference.resolve() instanceof PyParameter))
+ return true;
+ }
+ return false;
+ }
+
+ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
+ PyExpression problemElement = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset() - 1), PyNamedParameter.class);
+ if (problemElement == null)
+ problemElement = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()-1), PyExpression.class);
+ if (problemElement != null) {
+ String name = problemElement.getName();
+ PsiReference reference = problemElement.getReference();
+ if (problemElement instanceof PyQualifiedExpression) {
+ final PyExpression qualifier = ((PyQualifiedExpression)problemElement).getQualifier();
+ if (qualifier != null) {
+ reference = qualifier.getReference();
+ name = qualifier.getText();
+ }
+ }
+ PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
+
+ PyParameter parameter = null;
+ if (problemElement instanceof PyParameter)
+ parameter = (PyParameter)problemElement;
+ else if (reference!= null && reference.resolve() instanceof PyParameter) {
+ parameter = (PyParameter)reference.resolve();
+ }
+ if (parameter != null && name != null) {
+ final PyFunction function =
+ elementGenerator.createFromText(LanguageLevel.forElement(problemElement), PyFunction.class,
+ "def foo(" + name + ": object):\n\tpass");
+ final PyNamedParameter namedParameter = function.getParameterList().findParameterByName(name);
+ assert namedParameter != null;
+ parameter = (PyParameter)parameter.replace(namedParameter);
+ parameter = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(parameter);
+ editor.getCaretModel().moveToOffset(parameter.getTextOffset());
+
+ final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(parameter);
+ builder.replaceRange(TextRange.create(parameter.getTextLength()-PyNames.OBJECT.length(), parameter.getTextLength()), PyNames.OBJECT);
+ Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate();
+ TemplateManager.getInstance(project).startTemplate(editor, template);
+ }
+ }
+ }
+
+ public boolean startInWriteAction() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/python/testData/intentions/afterTypeInPy3Annotation.py b/python/testData/intentions/afterTypeInPy3Annotation.py
new file mode 100644
index 000000000000..53a85344911b
--- /dev/null
+++ b/python/testData/intentions/afterTypeInPy3Annotation.py
@@ -0,0 +1,3 @@
+def foo(a: object, b):
+ a.
+ b = 1
\ No newline at end of file
diff --git a/python/testData/intentions/beforeTypeInPy3Annotation.py b/python/testData/intentions/beforeTypeInPy3Annotation.py
new file mode 100644
index 000000000000..f50b75c1aec9
--- /dev/null
+++ b/python/testData/intentions/beforeTypeInPy3Annotation.py
@@ -0,0 +1,3 @@
+def foo(a, b):
+ a.
+ b = 1
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/PyIntentionTest.java
index 33a0ecb0f03c..6bc39186cd7c 100644
--- a/python/testSrc/com/jetbrains/python/PyIntentionTest.java
+++ b/python/testSrc/com/jetbrains/python/PyIntentionTest.java
@@ -230,6 +230,10 @@ public class PyIntentionTest extends PyTestCase {
doTest(PyBundle.message("INTN.specify.type"));
}
+ public void testTypeInPy3Annotation() { //PY-7045
+ doTest(PyBundle.message("INTN.specify.type.in.annotation"), LanguageLevel.PYTHON32);
+ }
+
public void testTypeAssertion() {
doTest(PyBundle.message("INTN.insert.assertion"));
}