PY-18598 Support function type annotation of format: (...) -> rtype

This commit is contained in:
Mikhail Golubev
2016-03-17 18:40:25 +03:00
committed by Andrey Vlasovskikh
parent 13480914a4
commit c4d83386ed
3 changed files with 65 additions and 25 deletions
@@ -97,7 +97,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
final PyCallableType functionType = as(result.getType(), PyCallableType.class);
if (functionType != null) {
final List<PyCallableParameter> paramTypes = functionType.getParameters(context);
assert paramTypes != null;
// Function annotation of kind (...) -> Type
if (paramTypes == null) {
return Ref.create();
}
final PyParameter[] funcParams = func.getParameterList().getParameters();
final int startOffset = omitFirstParamInTypeComment(func) ? 1 : 0;
for (int paramIndex = 0; paramIndex < funcParams.length; paramIndex++) {
@@ -357,12 +360,13 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
paramListTypePositions.add(i);
}
}
if (!paramListTypePositions.isEmpty()) {
if (!("typing.Callable".equals(qualifiedName) && paramListTypePositions.equals(Collections.singletonList(0)))) {
return null;
}
}
if ("typing.Union".equals(qualifiedName)) {
return PyUnionType.union(elementTypes);
}
@@ -26,6 +26,7 @@ import com.intellij.psi.util.QualifiedName;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.PyTypingTypeProvider;
@@ -45,6 +46,7 @@ import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import static com.jetbrains.python.psi.PyUtil.as;
import static com.jetbrains.python.psi.types.PyTypeTokenTypes.IDENTIFIER;
import static com.jetbrains.python.psi.types.PyTypeTokenTypes.PARAMETER;
import static com.jetbrains.python.psi.types.functionalParser.FunctionalParserBase.*;
@@ -414,30 +416,46 @@ public class PyTypeParser {
})
.named("param-expr");
final FunctionalParser<ParseResult, PyElementType> ellipsis = op("...").map(token -> EMPTY_RESULT.withType(EllipsisType.INSTANCE));
final FunctionalParser<ParseResult, PyElementType> paramTypes = argExpr.then(many(op(",").skipThen(argExpr))).map(pair -> {
final List<PyType> types = new ArrayList<>();
final ParseResult first = pair.getFirst();
final List<ParseResult> second = pair.getSecond();
ParseResult result = first;
types.add(first.getType());
for (ParseResult r : second) {
result = result.merge(r);
types.add(r.getType());
}
return result.withType(new PyTypeParser.ParameterListType(types));
});
final FunctionalParser<ParseResult, PyElementType> funcExpr =
op("(").skipThen(maybe(argExpr.then(many(op(",").skipThen(argExpr))))).thenSkip(op(")")).thenSkip(op("->")).then(typeExpr)
.map(value -> {
final List<PyCallableParameter> parameters = new ArrayList<PyCallableParameter>();
final ParseResult returnResult = value.getSecond();
ParseResult result;
final Pair<ParseResult, List<ParseResult>> firstPair = value.getFirst();
if (firstPair != null) {
final ParseResult first = firstPair.getFirst();
final List<ParseResult> second = firstPair.getSecond();
result = first;
parameters.add(new PyCallableParameterImpl(null, first.getType()));
for (ParseResult r : second) {
result = result.merge(r);
parameters.add(new PyCallableParameterImpl(null, r.getType()));
}
result = result.merge(returnResult);
}
else {
result = returnResult;
}
return result.withType(new PyCallableTypeImpl(parameters, returnResult.getType()));
})
.named("func-expr");
op("(").skipThen(maybe(paramTypes.or(ellipsis))).thenSkip(op(")"))
.thenSkip(op("->")).then(typeExpr)
.map(value -> {
final ParseResult paramsResult = value.getFirst(), returnResult = value.getSecond();
final List<PyCallableParameter> parameters;
ParseResult result = returnResult;
if (paramsResult != null) {
result = result.merge(paramsResult);
final ParameterListType paramsType = as(paramsResult.getType(), ParameterListType.class);
if (paramsType != null) {
parameters = ContainerUtil.map(paramsType.getTypes(), type -> new PyCallableParameterImpl(null, type));
}
// ellipsis
else {
parameters = null;
}
}
else {
parameters = Collections.emptyList();
result = returnResult;
}
return result.withType(new PyCallableTypeImpl(parameters, returnResult.getType()));
})
.named("func-expr");
final FunctionalParser<ParseResult, PyElementType> typeFile =
funcExpr
@@ -758,4 +776,12 @@ public class PyTypeParser {
return myTypes;
}
}
public static class EllipsisType extends PyTypeAdapter {
public static final EllipsisType INSTANCE = new EllipsisType();
private EllipsisType() {
}
}
}
@@ -504,6 +504,16 @@ public class PyTypingTest extends PyTestCase {
}
// PY-18598
public void testFunctionTypeCommentEllipsisParameters() {
doTest("(x: Any, y: Any, z: Any) -> int",
"def f(x, y=42, z='foo'):\n" +
" # type: (...) -> int \n" +
" pass\n" +
"\n" +
"expr = f");
}
// PY-18386
public void testRecursiveType() {
doTest("Union[int, Any]",