diff --git a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java index 3ab14e450538..e2f046dc43fc 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/PyTypingTypeProvider.java @@ -97,7 +97,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { final PyCallableType functionType = as(result.getType(), PyCallableType.class); if (functionType != null) { final List 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); } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index bedeaf25d1a3..e173d0343372 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -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 ellipsis = op("...").map(token -> EMPTY_RESULT.withType(EllipsisType.INSTANCE)); + + final FunctionalParser paramTypes = argExpr.then(many(op(",").skipThen(argExpr))).map(pair -> { + final List types = new ArrayList<>(); + final ParseResult first = pair.getFirst(); + final List 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 funcExpr = - op("(").skipThen(maybe(argExpr.then(many(op(",").skipThen(argExpr))))).thenSkip(op(")")).thenSkip(op("->")).then(typeExpr) - .map(value -> { - final List parameters = new ArrayList(); - final ParseResult returnResult = value.getSecond(); - ParseResult result; - final Pair> firstPair = value.getFirst(); - if (firstPair != null) { - final ParseResult first = firstPair.getFirst(); - final List 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 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 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() { + } + } + } diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 0106959a59b5..96346cdb1433 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -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]",