PY-22005 Fixed: Show types in parameter hints

Append expected argument type to parameter info
Details:
`Any` is excluded from `Union[Any, ...]`-like types
`None` default value is hidden because it will be listed in the type as `Optional[...]` or `Union[..., None, ...]`
This commit is contained in:
Semyon Proshev
2017-01-25 15:19:35 +03:00
committed by Semyon Proshev
parent 60e9609754
commit a6383951ff
9 changed files with 196 additions and 87 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -19,7 +19,10 @@ import com.intellij.psi.PsiNameIdentifierOwner;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.psi.stubs.PyNamedParameterStub;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a named parameter, as opposed to a tuple parameter.
@@ -39,10 +42,30 @@ public interface PyNamedParameter extends PyParameter, PsiNamedElement, PsiNameI
boolean isKeywordOnly();
/**
* @param includeDefaultValue if true, include the default value after an " = ".
* @return Canonical representation of parameter. Includes asterisks for *param and **param, and name.
* @param includeDefaultValue if true, include the default value after an "=".
* @return canonical representation of parameter.
* Includes asterisks for *param and **param, and name.
*/
@NotNull
String getRepr(boolean includeDefaultValue);
default String getRepr(boolean includeDefaultValue) {
return getRepr(includeDefaultValue, null);
}
/**
* @param includeDefaultValue if true, include the default value after an "=".
* @param context context to be used to resolve argument type
* @return canonical representation of parameter.
* Includes asterisks for *param and **param, and name.
* Also includes argument type if {@code context} is not null and resolved type is not unknown.
*/
@NotNull
String getRepr(boolean includeDefaultValue, @Nullable TypeEvalContext context);
/**
* @param context context to be used to resolve argument type
* @return argument type. Returns element type for *param and value type for **param.
*/
@Nullable
PyType getArgumentType(@NotNull TypeEvalContext context);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -166,7 +166,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
// formatting of hints: hint index -> flags. this includes flags for parens.
final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags = new HashMap<>();
final List<String> hintsList = buildParameterListHint(parameterList, namedParameters, parameterToIndex, hintFlags);
final List<String> hintsList = buildParameterListHint(parameterList, namedParameters, parameterToIndex, hintFlags, typeEvalContext);
final int currentParamOffset = context.getCurrentParameterIndex(); // in Python mode, we get an offset here, not an index!
@@ -324,15 +324,17 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
/**
* builds the textual picture and the list of named parameters
*
* @param parameters parameters of a callable
* @param namedParameters used to collect all named parameters of callable
* @param parameters parameters of a callable
* @param namedParameters used to collect all named parameters of callable
* @param parameterToIndex used to collect info about parameter indexes
* @param hintFlags mark parameter as deprecated/highlighted/strikeout
* @param hintFlags mark parameter as deprecated/highlighted/strikeout
* @param context context to be used to get parameter representation
*/
private static List<String> buildParameterListHint(@NotNull List<PyParameter> parameters,
@NotNull final List<PyNamedParameter> namedParameters,
@NotNull final Map<PyNamedParameter, Integer> parameterToIndex,
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags) {
@NotNull final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags,
@NotNull TypeEvalContext context) {
final List<String> hintsList = new ArrayList<>();
ParamHelper.walkDownParamArray(
parameters.toArray(new PyParameter[parameters.size()]),
@@ -350,7 +352,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
namedParameters.add(param);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(param.getRepr(true));
stringBuilder.append(param.getRepr(true, context));
if (!last) stringBuilder.append(", ");
int hintIndex = hintsList.size();
parameterToIndex.put(param, hintIndex);
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -43,17 +43,25 @@ public class PyTypeModelBuilder {
}
abstract static class TypeModel {
abstract void accept(TypeVisitor visitor);
abstract void accept(@NotNull TypeVisitor visitor);
@NotNull
public String asString() {
TypeToStringVisitor visitor = new TypeToStringVisitor();
this.accept(visitor);
final TypeToStringVisitor visitor = new TypeToStringVisitor();
accept(visitor);
return visitor.getString();
}
public void toBodyWithLinks(@NotNull ChainIterable<String> body, @NotNull PsiElement anchor) {
TypeToBodyWithLinksVisitor visitor = new TypeToBodyWithLinksVisitor(body, anchor);
this.accept(visitor);
final TypeToBodyWithLinksVisitor visitor = new TypeToBodyWithLinksVisitor(body, anchor);
accept(visitor);
}
@NotNull
public String asDescription() {
final TypeToDescriptionVisitor visitor = new TypeToDescriptionVisitor();
accept(visitor);
return visitor.getDescription();
}
}
@@ -65,7 +73,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.oneOf(this);
}
}
@@ -80,7 +88,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.collectionOf(this);
}
}
@@ -93,7 +101,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.name(this.name);
}
}
@@ -106,7 +114,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.unknown(this);
}
}
@@ -119,7 +127,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.optional(this);
}
}
@@ -134,7 +142,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.tuple(this);
}
}
@@ -158,7 +166,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.function(this);
}
}
@@ -174,7 +182,7 @@ public class PyTypeModelBuilder {
}
@Override
void accept(TypeVisitor visitor) {
void accept(@NotNull TypeVisitor visitor) {
visitor.param(this);
}
}
@@ -359,6 +367,27 @@ public class PyTypeModelBuilder {
}
}
private static class TypeToDescriptionVisitor extends TypeNameVisitor {
@NotNull
private final StringBuilder myResult = new StringBuilder();
@Override
protected void add(String s) {
myResult.append(s);
}
@Override
protected void addType(String name) {
add(name);
}
@NotNull
public String getDescription() {
return myResult.toString();
}
}
private abstract static class TypeNameVisitor implements TypeVisitor {
private int myDepth = 0;
private final static int MAX_DEPTH = 6;
@@ -214,6 +214,17 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
buildTypeModel(type, context).toBodyWithLinks(body, anchor);
}
/**
* @param type type which description will be calculated
* @param context type evaluation context
* @return more user-friendly description than result of {@link PythonDocumentationProvider#getTypeName(PyType, TypeEvalContext)}.
* <code>Any</code> is excluded from <code>Union[Any, ...]</code>-like types.
*/
@NotNull
public static String getTypeDescription(@Nullable PyType type, @NotNull TypeEvalContext context) {
return buildTypeModel(type, context).asDescription();
}
@NotNull
private static PyTypeModelBuilder.TypeModel buildTypeModel(@Nullable PyType type, @NotNull TypeEvalContext context) {
return new PyTypeModelBuilder(context).build(type, true);
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -189,10 +189,8 @@ public class PyTypeCheckerInspection extends PyInspection {
problemsSet.add(checkMapping(results.getReceiver(), results.getArguments()));
}
if (!problemsSet.isEmpty()) {
Map<PyExpression, Pair<String, ProblemHighlightType>> minProblems = Collections.min(
problemsSet,
(o1, o2) -> o1.size() - o2.size()
);
final Map<PyExpression, Pair<String, ProblemHighlightType>> minProblems = Collections.min(problemsSet,
Comparator.comparingInt(Map::size));
for (Map.Entry<PyExpression, Pair<String, ProblemHighlightType>> entry : minProblems.entrySet()) {
registerProblem(entry.getKey(), entry.getValue().getFirst(), entry.getValue().getSecond());
}
@@ -222,7 +220,7 @@ public class PyTypeCheckerInspection extends PyInspection {
for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.entrySet()) {
final PyNamedParameter param = entry.getValue();
final PyExpression arg = entry.getKey();
final PyType expectedArgType = PyTypeChecker.getExpectedArgumentType(param, myTypeEvalContext);
final PyType expectedArgType = param.getArgumentType(myTypeEvalContext);
if (expectedArgType == null) {
continue;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -28,6 +28,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
@@ -35,6 +36,7 @@ import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.codeInsight.PyTypingTypeProvider;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.documentation.PythonDocumentationProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.stubs.PyNamedParameterStub;
@@ -166,13 +168,25 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
}
@NotNull
public String getRepr(boolean includeDefaultValue) {
StringBuilder sb = new StringBuilder();
@Override
public String getRepr(boolean includeDefaultValue, @Nullable TypeEvalContext context) {
final StringBuilder sb = new StringBuilder();
if (isPositionalContainer()) sb.append("*");
else if (isKeywordContainer()) sb.append("**");
sb.append(getName());
if (context != null) {
final PyType argumentType = getArgumentType(context);
if (argumentType != null) {
sb.append(": ");
sb.append(PythonDocumentationProvider.getTypeDescription(argumentType, context));
}
}
final PyExpression defaultValue = getDefaultValue();
if (includeDefaultValue && defaultValue != null) {
if (defaultValueShouldBeIncluded(includeDefaultValue, defaultValue)) {
String representation = PyUtil.getReadableRepr(defaultValue, true);
if (defaultValue instanceof PyStringLiteralExpression) {
final Pair<String, String> quotes = PyStringLiteralUtil.getQuotes(defaultValue.getText());
@@ -182,9 +196,36 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
}
sb.append("=").append(representation);
}
return sb.toString();
}
private static boolean defaultValueShouldBeIncluded(boolean includeDefaultValue, @Nullable PyExpression defaultValue) {
if (!includeDefaultValue || defaultValue == null) return false;
// In case of `None` default value, it will be listed in the type as `Optional[...]` or `Union[..., None, ...]`
return !PyNames.NONE.equals(defaultValue.getText());
}
@Override
@Nullable
public PyType getArgumentType(@NotNull TypeEvalContext context) {
final PyType parameterType = context.getType(this);
if (parameterType instanceof PyCollectionType) {
final PyCollectionType paramCollectionType = (PyCollectionType)parameterType;
if (isPositionalContainer()) {
return paramCollectionType.getIteratedItemType();
}
else if (isKeywordContainer()) {
return ContainerUtil.getOrElse(paramCollectionType.getElementTypes(context), 1, null);
}
}
return parameterType;
}
@Override
public PyAnnotation getAnnotation() {
return getStubOrPsiChild(PyElementTypes.ANNOTATION);
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -487,18 +487,18 @@ public class PyTypeChecker {
if (keywordParameter == null) keywordParameter = parameter;
keywordTypes.add(actualArgType);
}
else if (!match(getExpectedArgumentType(parameter, context), actualArgType, context, substitutions)) {
else if (!match(parameter.getArgumentType(context), actualArgType, context, substitutions)) {
return null;
}
}
if (positionalParameter != null &&
!match(getExpectedArgumentType(positionalParameter, context), PyUnionType.union(positionalTypes), context, substitutions)) {
!match(positionalParameter.getArgumentType(context), PyUnionType.union(positionalTypes), context, substitutions)) {
return null;
}
if (keywordParameter != null &&
!match(getExpectedArgumentType(keywordParameter, context), PyUnionType.union(keywordTypes), context, substitutions)) {
!match(keywordParameter.getArgumentType(context), PyUnionType.union(keywordTypes), context, substitutions)) {
return null;
}
@@ -757,24 +757,6 @@ public class PyTypeChecker {
}
}
@Nullable
public static PyType getExpectedArgumentType(@NotNull PyNamedParameter parameter, @NotNull TypeEvalContext context) {
final PyType parameterType = context.getType(parameter);
if (parameterType instanceof PyCollectionType) {
final PyCollectionType paramCollectionType = (PyCollectionType)parameterType;
if (parameter.isPositionalContainer()) {
return paramCollectionType.getIteratedItemType();
}
else if (parameter.isKeywordContainer()) {
return ContainerUtil.getOrElse(paramCollectionType.getElementTypes(context), 1, null);
}
}
return parameterType;
}
public static class AnalyzeCallResults {
@NotNull private final PyCallable myCallable;
@Nullable private final PyExpression myReceiver;
@@ -0,0 +1,7 @@
from typing import Union, Any
def foo(a1: str, a2: str=None, a3: Union[str, int]=None, a4: Union[Any, int], *args: int, **kwargs: int):
pass
foo(<arg1>)
@@ -29,6 +29,7 @@ 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.LanguageLevel;
import com.jetbrains.python.psi.PyArgumentList;
import com.jetbrains.python.psi.PyCallExpression;
import org.jetbrains.annotations.NotNull;
@@ -151,7 +152,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testKwdArgInClass() {
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, **kw", new String[]{"**kw"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: A, **kw", new String[]{"**kw"}, new String[]{"self: A, "});
}
public void testKwdArgOutOfOrder() {
@@ -222,14 +223,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testSimpleMethod() {
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a", new String[]{"a"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: A, a", new String[]{"a"}, new String[]{"self: A, "});
}
public void testSimpleClassFunction() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a", new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a", new String[]{"a"});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: A, a", new String[]{"self: A, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self: A, a", new String[]{"a"});
}
public void testReassignedFunction() {
@@ -242,65 +243,65 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testReassignedInstanceMethod() {
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b, c", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b, c", new String[]{"b, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("self, a, b, c", new String[]{"c"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: Foo, a, b, c", new String[]{"a, "}, new String[]{"self: Foo, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self: Foo, a, b, c", new String[]{"b, "}, new String[]{"self: Foo, "});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("self: Foo, a, b, c", new String[]{"c"}, new String[]{"self: Foo, "});
}
public void testReassignedClassInit() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: Bar, a, b", new String[]{"a, "}, new String[]{"self: Bar, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self: Bar, a, b", new String[]{"b"}, new String[]{"self: Bar, "});
}
public void testInheritedClassInit() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: Bar, a, b", new String[]{"a, "}, new String[]{"self: Bar, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self: Bar, a, b", new String[]{"b"}, new String[]{"self: Bar, "});
}
public void testRedefinedNewConstructorCall() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls, a, b", new String[]{"a, "}, new String[]{"cls, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls, a, b", new String[]{"b"}, new String[]{"cls, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls: A, a, b", new String[]{"a, "}, new String[]{"cls: A, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls: A, a, b", new String[]{"b"}, new String[]{"cls: A, "});
}
public void testRedefinedNewDirectCall() {
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls, a, b", new String[]{"cls, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls, a, b", new String[]{"a, "});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("cls, a, b", new String[]{"b"});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls: A, a, b", new String[]{"cls: A, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls: A, a, b", new String[]{"a, "});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("cls: A, a, b", new String[]{"b"});
}
public void testIgnoreNewInOldStyleClass() {
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, one", new String[]{"one"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: A, one", new String[]{"one"}, new String[]{"self: A, "});
}
public void testBoundMethodSimple() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: A, a, b", new String[]{"a, "}, new String[]{"self: A, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self: A, a, b", new String[]{"b"}, new String[]{"self: A, "});
}
public void testBoundMethodReassigned() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self: A, a, b", new String[]{"a, "}, new String[]{"self: A, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self: A, a, b", new String[]{"b"}, new String[]{"self: A, "});
}
public void testConstructorFactory() {
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg>").getTextOffset()).check("self, color", new String[]{"color"}, new String[]{"self, "});
feignCtrlP(marks.get("<arg>").getTextOffset()).check("self: Foo, color", new String[]{"color"}, new String[]{"self: Foo, "});
}
@@ -385,16 +386,16 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testPartialNamed() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c=1, d=2, e=3", new String[]{"d=2, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, c=1, d=2, e=3", new String[]{"e=3"}); // no logical next
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c: int=1, d: int=2, e: int=3", new String[]{"d: int=2, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, c: int=1, d: int=2, e: int=3", new String[]{"e: int=3"}); // no logical next
}
public void testPy3kPastTupleArg() {
Map<String, PsiElement> marks = loadTest(4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("*arg, a=1, b=2", new String[]{"*arg, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("*arg, a=1, b=2", new String[]{"*arg, "});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("*arg, a=1, b=2", new String[]{"b=2"});
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("*arg, a=1, b=2", new String[]{"a=1, "});
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("*arg, a: int=1, b: int=2", new String[]{"*arg, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("*arg, a: int=1, b: int=2", new String[]{"*arg, "});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("*arg, a: int=1, b: int=2", new String[]{"b: int=2"});
feignCtrlP(marks.get("<arg4>").getTextOffset()).check("*arg, a: int=1, b: int=2", new String[]{"a: int=1, "});
}
public void testNoArgs() {
@@ -409,11 +410,26 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testMultilineStringDefault() {
final int offset = loadTest(1).get("<arg2>").getTextOffset();
feignCtrlP(offset).check("length=12, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'",
new String[]{"allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'"},
feignCtrlP(offset).check("length: int=12, allowed_chars: str='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'",
new String[]{"allowed_chars: str='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'"},
ArrayUtil.EMPTY_STRING_ARRAY);
}
// PY-22005
public void testWithSpecifiedType() {
myFixture.copyDirectoryToProject("typing", "");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
final String expectedInfo = "a1: str, a2: Optional[str], a3: Union[str, int, None], a4: int, *args: int, **kwargs: int";
feignCtrlP(offset).check(expectedInfo, new String[]{"a1: str, "});
}
);
}
/**
* Imitates pressing of Ctrl+P; fails if results are not as expected.
* @param offset offset of 'cursor' where ^P is pressed.