diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index c12c3329c23f..27843319b227 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -295,6 +295,8 @@ INTN.convert.static.method.to.function=Convert static method to function #PyConvertMethodToPropertyIntention INTN.convert.method.to.property=Convert method to property +INTN.annotate.variable.type.PEP484.incompatible.type=The type ''{0}'' cannot be expressed in PEP 484 format + # Conflict checker CONFLICT.name.$0.obscured=Name ''{0}'' obscured by local definitions CONFLICT.name.$0.obscured.cannot.convert=Name ''{0}'' obscured. Cannot convert. diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyAnnotateVariableTypeIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyAnnotateVariableTypeIntention.java index f0bb668bf60c..a96db78ab460 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/PyAnnotateVariableTypeIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyAnnotateVariableTypeIntention.java @@ -2,6 +2,7 @@ package com.jetbrains.python.codeInsight.intentions; import com.intellij.codeInsight.TargetElementUtil; +import com.intellij.codeInsight.hint.HintManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; @@ -17,6 +18,7 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.codeInsight.intentions.PyTypeHintGenerationUtil.AnnotationInfo; +import com.jetbrains.python.codeInsight.intentions.PyTypeHintGenerationUtil.Pep484IncompatibleTypeException; import com.jetbrains.python.documentation.PythonDocumentationProvider; import com.jetbrains.python.documentation.doctest.PyDocstringFile; import com.jetbrains.python.psi.*; @@ -192,11 +194,16 @@ public class PyAnnotateVariableTypeIntention extends PyBaseIntentionAction { final List targets = findSuitableTargetsUnderCaret(project, editor, file); assert targets.size() == 1; final PyTargetExpression annotationTarget = targets.get(0); - if (preferSyntacticAnnotation(annotationTarget)) { - insertVariableAnnotation(annotationTarget); + try { + if (preferSyntacticAnnotation(annotationTarget)) { + insertVariableAnnotation(annotationTarget); + } + else { + insertVariableTypeComment(annotationTarget); + } } - else { - insertVariableTypeComment(annotationTarget); + catch (Pep484IncompatibleTypeException e) { + HintManager.getInstance().showErrorHint(editor, e.getMessage()); } } @@ -207,6 +214,7 @@ public class PyAnnotateVariableTypeIntention extends PyBaseIntentionAction { private static void insertVariableAnnotation(@NotNull PyTargetExpression target) { final TypeEvalContext context = TypeEvalContext.userInitiated(target.getProject(), target.getContainingFile()); final PyType inferredType = context.getType(target); + PyTypeHintGenerationUtil.checkPep484Compatibility(inferredType); final String annotationText = PythonDocumentationProvider.getTypeName(inferredType, context); final AnnotationInfo info = new AnnotationInfo(annotationText, inferredType); if (isInstanceAttribute(target, context)) { @@ -277,6 +285,7 @@ public class PyAnnotateVariableTypeIntention extends PyBaseIntentionAction { } else if (target instanceof PyTypedElement) { final PyType singleTargetType = context.getType((PyTypedElement)target); + PyTypeHintGenerationUtil.checkPep484Compatibility(singleTargetType); final String singleTargetAnnotation = PythonDocumentationProvider.getTypeName(singleTargetType, context); types.add(singleTargetType); typeRanges.add(TextRange.from(builder.length(), singleTargetAnnotation.length())); diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyTypeHintGenerationUtil.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyTypeHintGenerationUtil.java index b36fbcba8be1..ad8871681322 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/PyTypeHintGenerationUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyTypeHintGenerationUtil.java @@ -17,6 +17,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ThrowableRunnable; +import com.jetbrains.python.PyBundle; import com.jetbrains.python.codeInsight.imports.AddImportHelper; import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority; import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider; @@ -320,6 +321,38 @@ public class PyTypeHintGenerationUtil { } } + public static void checkPep484Compatibility(@Nullable PyType type) { + if (type == null || + type instanceof PyNoneType || + type instanceof PyCallableTypeImpl || + type instanceof PyGenericType || + type instanceof PyFunctionType) { + return; + } + else if (type instanceof PyUnionType) { + for (PyType memberType : ((PyUnionType)type).getMembers()) { + checkPep484Compatibility(memberType); + } + } + else if (type instanceof PyCollectionType) { + for (PyType typeParam : ((PyCollectionType)type).getElementTypes()) { + checkPep484Compatibility(typeParam); + } + } + else if (type instanceof PyClassType) { + // In this order since PyCollectionTypeImpl implements PyClassType + } + else { + throw new Pep484IncompatibleTypeException(PyBundle.message("INTN.annotate.variable.type.PEP484.incompatible.type", type.getName())); + } + } + + public static final class Pep484IncompatibleTypeException extends RuntimeException { + public Pep484IncompatibleTypeException(String message) { + super(message); + } + } + public static final class AnnotationInfo { private final String myAnnotationText; private final List myTypes; diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForNestedStructuralType.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForNestedStructuralType.py new file mode 100644 index 000000000000..dbe05bcf62ed --- /dev/null +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForNestedStructuralType.py @@ -0,0 +1,3 @@ +def func(x): + x.foo() + var = [x] \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForNestedStructuralType_after.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForNestedStructuralType_after.py new file mode 100644 index 000000000000..9a2248e1a674 --- /dev/null +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForNestedStructuralType_after.py @@ -0,0 +1,3 @@ +def func(x): + x.foo() + var = [x] \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForStructuralType.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForStructuralType.py new file mode 100644 index 000000000000..b6d611457f03 --- /dev/null +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForStructuralType.py @@ -0,0 +1,3 @@ +def func(x): + x.foo() + var = x \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForStructuralType_after.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForStructuralType_after.py new file mode 100644 index 000000000000..73b0bfb02798 --- /dev/null +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationNotPossibleForStructuralType_after.py @@ -0,0 +1,3 @@ +def func(x): + x.foo() + var = x \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/intentions/PyAnnotateVariableTypeIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/PyAnnotateVariableTypeIntentionTest.java index f316130026ff..dbb9fab09b28 100644 --- a/python/testSrc/com/jetbrains/python/intentions/PyAnnotateVariableTypeIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/intentions/PyAnnotateVariableTypeIntentionTest.java @@ -215,6 +215,14 @@ public class PyAnnotateVariableTypeIntentionTest extends PyIntentionTestCase { doAnnotationTest(); } + public void testAnnotationNotPossibleForStructuralType() { + doAnnotationTest(); + } + + public void testAnnotationNotPossibleForNestedStructuralType() { + doAnnotationTest(); + } + private void doAnnotationTest() { doTest(LanguageLevel.PYTHON36); }