PY-27392 Better names of intentions for functions and variable type hints

Now it's possible to distinguish between them when both are offered
on a target of a call expression result assignment (as it was noticed
during the testing of PY-16412).
This commit is contained in:
Mikhail Golubev
2018-02-05 21:41:59 +03:00
committed by Andrey Vlasovskikh
parent 149eefa75e
commit b7ebc74682
9 changed files with 47 additions and 36 deletions
@@ -280,8 +280,12 @@ INTN.add.parameters.to.docstring=Add parameters to docstring
INTN.specify.type.in.annotation=Specify type for reference using annotation
INTN.specify.return.type.in.annotation=Specify return type using annotation
#PyAnnotateTypesIntention
INTN.annotate.types=Annotate types
#PyAnnotateTypesIntention/PyAnnotateVariableTypeIntention
INTN.add.type.hints.for.function.family=Add type hints for function
INTN.add.type.hints.for.function=Add type hints for function ''{0}''
INTN.add.type.hint.for.variable.family=Add type hint for variable
INTN.add.type.hint.for.variable=Add type hint for variable ''{0}''
INTN.add.type.hint.for.variable.PEP484.incompatible.type=Type ''{0}'' cannot be expressed inline in PEP 484 format
#TypeAssertionIntention
INTN.insert.assertion=Insert type assertion
@@ -295,8 +299,6 @@ 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.
@@ -38,32 +38,26 @@ import java.util.List;
import static com.jetbrains.python.codeInsight.intentions.SpecifyTypeInPy3AnnotationsIntention.*;
import static com.jetbrains.python.codeInsight.intentions.TypeIntention.getMultiCallable;
import static com.jetbrains.python.codeInsight.intentions.TypeIntention.resolvesToFunction;
/**
* @author traff
*/
public class PyAnnotateTypesIntention extends PyBaseIntentionAction {
public PyAnnotateTypesIntention() {
setText(PyBundle.message("INTN.annotate.types"));
}
@NotNull
public String getFamilyName() {
return PyBundle.message("INTN.annotate.types");
return PyBundle.message("INTN.add.type.hints.for.function.family");
}
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
if (!(file instanceof PyFile) || file instanceof PyDocstringFile) return false;
updateText();
final PsiElement elementAt = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset());
if (elementAt == null) return false;
if (resolvesToFunction(elementAt, input -> true)) {
updateText();
final PyFunction function = TypeIntention.findSuitableFunction(elementAt, input -> true);
if (function != null) {
setText(PyBundle.message("INTN.add.type.hints.for.function", function.getName()));
return true;
}
return false;
@@ -221,8 +215,4 @@ public class PyAnnotateTypesIntention extends PyBaseIntentionAction {
startTemplate(project, callable, builder);
}
}
protected void updateText() {
setText(PyBundle.message("INTN.annotate.types"));
}
}
@@ -44,7 +44,7 @@ public class PyAnnotateVariableTypeIntention extends PyBaseIntentionAction {
@NotNull
@Override
public String getFamilyName() {
return PyBundle.message("INTN.annotate.types");
return PyBundle.message("INTN.add.type.hint.for.variable.family");
}
@Override
@@ -57,7 +57,7 @@ public class PyAnnotateVariableTypeIntention extends PyBaseIntentionAction {
return false;
}
setText(PyBundle.message("INTN.annotate.types"));
setText(PyBundle.message("INTN.add.type.hint.for.variable", resolved.get(0).getName()));
return true;
}
@@ -343,7 +343,7 @@ public class PyTypeHintGenerationUtil {
// In this order since PyCollectionTypeImpl implements PyClassType
}
else {
throw new Pep484IncompatibleTypeException(PyBundle.message("INTN.annotate.variable.type.PEP484.incompatible.type", type.getName()));
throw new Pep484IncompatibleTypeException(PyBundle.message("INTN.add.type.hint.for.variable.PEP484.incompatible.type", type.getName()));
}
}
@@ -111,25 +111,28 @@ public abstract class TypeIntention extends PyBaseIntentionAction {
}
private boolean isAvailableForReturn(@NotNull final PsiElement elementAt) {
return resolvesToFunction(elementAt, input -> !isReturnTypeDefined(input));
return findSuitableFunction(elementAt, input -> !isReturnTypeDefined(input)) != null;
}
static boolean resolvesToFunction(@NotNull PsiElement elementAt, Function<PyFunction, Boolean> isAvailableForFunction) {
@Nullable
public static PyFunction findSuitableFunction(@NotNull PsiElement elementAt, Function<PyFunction, Boolean> isAvailableForFunction) {
final PyFunction parentFunction = PsiTreeUtil.getParentOfType(elementAt, PyFunction.class);
if (parentFunction != null) {
final ASTNode nameNode = parentFunction.getNameNode();
if (nameNode != null) {
final PsiElement prev = elementAt.getContainingFile().findElementAt(elementAt.getTextOffset()-1);
if (nameNode.getPsi() == elementAt || nameNode.getPsi() == prev) {
return isAvailableForFunction.apply(parentFunction);
if (isAvailableForFunction.apply(parentFunction)) {
return parentFunction;
}
}
}
}
final PyCallExpression callExpression = getCallExpression(elementAt);
if (callExpression == null) return false;
if (callExpression == null) return null;
final PyExpression callee = callExpression.getCallee();
if (callee == null) return false;
if (callee == null) return null;
final PsiReference reference = callee.getReference();
if (reference instanceof PsiPolyVariantReference) {
final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false);
@@ -137,18 +140,20 @@ public abstract class TypeIntention extends PyBaseIntentionAction {
if (results[i].getElement() instanceof PyFunction) {
final PsiElement result = results[i].getElement();
final PsiFile psiFile = result.getContainingFile();
if (psiFile == null) return false;
if (psiFile == null) return null;
final VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null) {
if (ProjectRootManager.getInstance(psiFile.getProject()).getFileIndex().isInLibraryClasses(virtualFile)) {
return false;
return null;
}
}
return isAvailableForFunction.apply((PyFunction)result);
if (isAvailableForFunction.apply((PyFunction)result)) {
return (PyFunction)result;
}
}
}
}
return false;
return null;
}
protected boolean isReturnTypeDefined(@NotNull PyFunction function) {
@@ -0,0 +1,5 @@
def func(x):
return 42
va<caret>r = func('foo')
@@ -0,0 +1,5 @@
def func(x):
return 42
var: [int] = func('foo')
@@ -20,17 +20,17 @@ public class PyAnnotateTypesIntentionTest extends PyIntentionTestCase {
runWithLanguageLevel(
LanguageLevel.PYTHON34,
() -> {
doIntentionTest(PyBundle.message("INTN.annotate.types"), getTestName(true) + ".py", "foo_decl.py");
doIntentionTest(PyBundle.message("INTN.add.type.hints.for.function.family"), getTestName(true) + ".py", "foo_decl.py");
myFixture.checkResultByFile("foo_decl.py", "foo_decl_after.py", false);
}
);
}
public void testTypeComment() {
doTest(PyBundle.message("INTN.annotate.types"), LanguageLevel.PYTHON27);
doTest(PyBundle.message("INTN.add.type.hints.for.function.family"), LanguageLevel.PYTHON27);
}
private void doTest() {
doTest(PyBundle.message("INTN.annotate.types"), LanguageLevel.PYTHON34);
doTest(PyBundle.message("INTN.add.type.hints.for.function.family"), LanguageLevel.PYTHON34);
}
}
@@ -227,6 +227,10 @@ public class PyAnnotateVariableTypeIntentionTest extends PyIntentionTestCase {
doAnnotationTest();
}
public void testConflictWithAnnotationFunctionTypeIntention() {
doTest(LanguageLevel.PYTHON36);
}
private void doAnnotationTest() {
doTest(LanguageLevel.PYTHON36);
}
@@ -236,12 +240,12 @@ public class PyAnnotateVariableTypeIntentionTest extends PyIntentionTestCase {
}
private void doNegativeTest() {
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> doNegativeTest(PyBundle.message("INTN.annotate.types")));
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> doNegativeTest(PyBundle.message("INTN.add.type.hint.for.variable.family")));
}
public void doMultiFileAnnotationTest() {
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> {
doMultiFileTest(PyBundle.message("INTN.annotate.types"));
doMultiFileTest(PyBundle.message("INTN.add.type.hint.for.variable.family"));
});
}
@@ -256,6 +260,6 @@ public class PyAnnotateVariableTypeIntentionTest extends PyIntentionTestCase {
}
private void doTest(@NotNull LanguageLevel languageLevel) {
doTest(PyBundle.message("INTN.annotate.types"), languageLevel);
doTest(PyBundle.message("INTN.add.type.hint.for.variable.family"), languageLevel);
}
}