Added function types syntax

This commit is contained in:
Andrey Vlasovskikh
2013-07-23 21:06:21 +04:00
parent 62b65602ac
commit ad8b160b9a
12 changed files with 268 additions and 42 deletions
@@ -96,6 +96,12 @@ public class PyJavaClassType implements PyClassLikeType {
return null;
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Override
public boolean isDefinition() {
return myDefinition;
@@ -39,6 +39,12 @@ public class PyJavaMethodType implements PyCallableType {
return PyJavaTypeProvider.asPyType(myMethod.getReturnType());
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Nullable
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@@ -4,6 +4,8 @@ import com.jetbrains.python.psi.PyQualifiedExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* A type instances of which can possibly be called. For example, a class definition can be called, and the result of a call is a class
* instance.
@@ -25,4 +27,12 @@ public interface PyCallableType extends PyType {
*/
@Nullable
PyType getCallType(@NotNull TypeEvalContext context, @Nullable PyQualifiedExpression callSite);
/**
* Returns the list of parameter types.
*
* @return list of positional parameters or null if not applicable.
*/
@Nullable
List<PyType> getParameterTypes(@NotNull TypeEvalContext context);
}
@@ -0,0 +1,82 @@
package com.jetbrains.python.psi.types;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyQualifiedExpression;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author vlan
*/
public class PyCallableTypeImpl implements PyCallableType {
@NotNull private final List<PyType> myParameterTypes;
@Nullable private final PyType myReturnType;
public PyCallableTypeImpl(@NotNull List<PyType> parameterTypes, @Nullable PyType returnType) {
myParameterTypes = parameterTypes;
myReturnType = returnType;
}
@Override
public boolean isCallable() {
return true;
}
@Nullable
@Override
public PyType getCallType(@NotNull TypeEvalContext context, @Nullable PyQualifiedExpression callSite) {
return myReturnType;
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return myParameterTypes;
}
@Nullable
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
return null;
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PyExpression location, ProcessingContext context) {
return new Object[0];
}
@Nullable
@Override
public String getName() {
return String.format("(%s) -> %s",
StringUtil.join(myParameterTypes,
new Function<PyType, String>() {
@Override
public String fun(PyType type) {
return type != null ? type.getName() : "unknown";
}
},
", "),
myReturnType != null ? myReturnType.getName() : "unknown");
}
@Override
public boolean isBuiltin(TypeEvalContext context) {
return false;
}
@Override
public void assertValid(String message) {
}
}
@@ -277,6 +277,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
return null;
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Nullable
private static PsiElement resolveClassMember(@NotNull PyClass cls,
boolean isDefinition,
@@ -33,6 +33,12 @@ public class PyFunctionType implements PyCallableType {
return myCallable.getReturnType(context, callSite);
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@@ -26,6 +26,6 @@ import static com.jetbrains.python.psi.types.PyTypeTokenTypes.*;
[\r\n]+ { return NL; }
[\ \t] { return SPACE; }
(":py"?":class:`"[~!]?)|("`")|([A-Z]"{")|("}") { return MARKUP; }
("or")|("of")|("from")|("to")|("<=")|[,\(\)\.\[\]|] { return OP; }
("or")|("of")|("from")|("to")|("<=")|("->")|[,\(\)\.\[\]|] { return OP; }
[T-Z] { return PARAMETER; }
[A-Za-z_][A-Za-z_0-9]* { return IDENTIFIER; }
@@ -140,6 +140,27 @@ public class PyTypeChecker {
return true;
}
}
if (actual instanceof PyCallableType && expected instanceof PyCallableType) {
final PyCallableType expectedCallable = (PyCallableType)expected;
final PyCallableType actualCallable = (PyCallableType)actual;
if (expectedCallable.isCallable() && actualCallable.isCallable()) {
final List<PyType> expectedParameters = expectedCallable.getParameterTypes(context);
final List<PyType> actualParameters = actualCallable.getParameterTypes(context);
if (expectedParameters != null && actualParameters != null) {
final int size = Math.min(expectedParameters.size(), actualParameters.size());
for (int i = 0; i < size; i++) {
if (!match(expectedParameters.get(i), actualParameters.get(i), context, substitutions, recursive)) {
return false;
}
}
}
if (!match(expectedCallable.getCallType(context, null), actualCallable.getCallType(context, null), context, substitutions,
recursive)) {
return false;
}
return true;
}
}
final String superName = expected.getName();
final String subName = actual.getName();
// TODO: No inheritance check for builtin numerics at this moment
@@ -1,4 +1,4 @@
/* The following code was generated by JFlex 1.4.3 on 7/22/13 3:52 PM */
/* The following code was generated by JFlex 1.4.3 on 7/23/13 5:34 PM */
package com.jetbrains.python.psi.types;
@@ -11,7 +11,7 @@ import static com.jetbrains.python.psi.types.PyTypeTokenTypes.*;
/**
* This class is a scanner generated by
* <a href="http://www.jflex.de/">JFlex</a> 1.4.3
* on 7/22/13 3:52 PM from the specification file
* on 7/23/13 5:34 PM from the specification file
* <tt>/home/user/src/idea/tools/lexer/../../python/src/com/jetbrains/python/psi/types/PyType.flex</tt>
*/
public class PyTypeLexer implements FlexLexer {
@@ -35,12 +35,12 @@ public class PyTypeLexer implements FlexLexer {
* Translates characters to character classes
*/
private static final String ZZ_CMAP_PACKED =
"\11\0\1\2\1\1\2\0\1\1\22\0\1\2\1\13\6\0\2\26"+
"\2\0\1\26\1\0\1\26\1\0\12\31\1\3\1\0\1\24\1\25"+
"\3\0\23\14\7\27\1\26\1\0\1\26\1\0\1\30\1\12\1\10"+
"\1\30\1\6\2\30\1\21\5\30\1\7\1\22\1\30\1\17\1\4"+
"\1\30\1\20\1\11\1\23\4\30\1\5\1\30\1\15\1\26\1\16"+
"\1\13\uff81\0";
"\11\0\1\2\1\1\2\0\1\1\22\0\1\2\1\13\6\0\2\30"+
"\2\0\1\30\1\26\1\30\1\0\12\33\1\3\1\0\1\24\1\25"+
"\1\27\2\0\23\14\7\31\1\30\1\0\1\30\1\0\1\32\1\12"+
"\1\10\1\32\1\6\2\32\1\21\5\32\1\7\1\22\1\32\1\17"+
"\1\4\1\32\1\20\1\11\1\23\4\32\1\5\1\32\1\15\1\30"+
"\1\16\1\13\uff81\0";
/**
* Translates characters to character classes
@@ -53,12 +53,12 @@ public class PyTypeLexer implements FlexLexer {
private static final int [] ZZ_ACTION = zzUnpackAction();
private static final String ZZ_ACTION_PACKED_0 =
"\1\0\1\1\1\2\1\0\1\3\1\4\4\3\1\0"+
"\1\0\1\1\1\2\1\0\1\3\1\4\4\3\2\0"+
"\1\5\1\6\2\0\1\5\1\3\2\0\1\3\5\0"+
"\1\4";
private static int [] zzUnpackAction() {
int [] result = new int[26];
int [] result = new int[27];
int offset = 0;
offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
return result;
@@ -83,13 +83,13 @@ public class PyTypeLexer implements FlexLexer {
private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
private static final String ZZ_ROWMAP_PACKED_0 =
"\0\0\0\32\0\64\0\116\0\150\0\64\0\202\0\234"+
"\0\266\0\320\0\352\0\64\0\202\0\u0104\0\u011e\0\150"+
"\0\u0138\0\u0152\0\u016c\0\u0186\0\u01a0\0\u01ba\0\u01d4\0\u01ee"+
"\0\u0208\0\u0222";
"\0\0\0\34\0\70\0\124\0\160\0\70\0\214\0\250"+
"\0\304\0\340\0\374\0\u0118\0\70\0\214\0\u0134\0\u0150"+
"\0\160\0\u016c\0\u0188\0\u01a4\0\u01c0\0\u01dc\0\u01f8\0\u0214"+
"\0\u0230\0\u024c\0\u0268";
private static int [] zzUnpackRowMap() {
int [] result = new int[26];
int [] result = new int[27];
int offset = 0;
offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
return result;
@@ -114,22 +114,22 @@ public class PyTypeLexer implements FlexLexer {
private static final String ZZ_TRANS_PACKED_0 =
"\1\0\1\2\1\3\1\4\6\5\1\6\1\0\1\7"+
"\1\0\1\6\1\10\1\5\1\11\1\5\1\12\1\13"+
"\1\0\1\14\1\15\1\5\2\0\1\2\66\0\1\16"+
"\1\0\1\17\27\0\6\5\2\0\1\5\2\0\5\5"+
"\3\0\3\5\4\0\6\5\2\0\1\5\1\6\1\0"+
"\5\5\3\0\3\5\4\0\6\5\2\0\1\5\2\0"+
"\1\5\2\20\2\5\3\0\3\5\4\0\6\5\2\0"+
"\1\5\2\0\1\5\1\21\3\5\3\0\3\5\4\0"+
"\6\5\2\0\1\5\2\0\1\20\4\5\3\0\3\5"+
"\25\0\1\14\11\0\1\22\33\0\1\23\26\0\6\5"+
"\2\0\1\5\2\0\1\24\4\5\3\0\3\5\3\0"+
"\1\25\36\0\1\26\25\0\6\5\2\0\1\5\2\0"+
"\3\5\1\20\1\5\3\0\3\5\6\0\1\17\34\0"+
"\1\27\31\0\1\30\23\0\1\31\40\0\1\32\32\0"+
"\1\6\16\0";
"\1\0\1\14\1\0\1\15\1\16\1\5\2\0\1\2"+
"\72\0\1\17\1\0\1\20\31\0\6\5\2\0\1\5"+
"\2\0\5\5\5\0\3\5\4\0\6\5\2\0\1\5"+
"\1\6\1\0\5\5\5\0\3\5\4\0\6\5\2\0"+
"\1\5\2\0\1\5\2\21\2\5\5\0\3\5\4\0"+
"\6\5\2\0\1\5\2\0\1\5\1\22\3\5\5\0"+
"\3\5\4\0\6\5\2\0\1\5\2\0\1\21\4\5"+
"\5\0\3\5\25\0\1\15\35\0\1\15\11\0\1\23"+
"\35\0\1\24\30\0\6\5\2\0\1\5\2\0\1\25"+
"\4\5\5\0\3\5\3\0\1\26\40\0\1\27\27\0"+
"\6\5\2\0\1\5\2\0\3\5\1\21\1\5\5\0"+
"\3\5\6\0\1\20\36\0\1\30\33\0\1\31\25\0"+
"\1\32\42\0\1\33\34\0\1\6\20\0";
private static int [] zzUnpackTrans() {
int [] result = new int[572];
int [] result = new int[644];
int offset = 0;
offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
return result;
@@ -170,11 +170,11 @@ public class PyTypeLexer implements FlexLexer {
private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
private static final String ZZ_ATTRIBUTE_PACKED_0 =
"\1\0\1\1\1\11\1\0\1\1\1\11\4\1\1\0"+
"\1\0\1\1\1\11\1\0\1\1\1\11\4\1\2\0"+
"\1\11\1\1\2\0\2\1\2\0\1\1\5\0\1\1";
private static int [] zzUnpackAttribute() {
int [] result = new int[26];
int [] result = new int[27];
int offset = 0;
offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
return result;
@@ -257,7 +257,7 @@ public class PyTypeLexer implements FlexLexer {
char [] map = new char[0x10000];
int i = 0; /* index in packed string */
int j = 0; /* index in unpacked array */
while (i < 104) {
while (i < 106) {
int count = packed.charAt(i++);
char value = packed.charAt(i++);
do map[j++] = value; while (--count > 0);
@@ -108,10 +108,11 @@ public class PyTypeParser {
final ForwardDeclaration<ParseResult, PyElementType> typeExpr = ForwardDeclaration.create();
final FunctionalParser<ParseResult, PyElementType> simpleType =
final FunctionalParser<ParseResult, PyElementType> classType =
token(IDENTIFIER).then(many(op(".").skipThen(token(IDENTIFIER))))
.map(new MakeSimpleType(anchor))
.named("simple-type");
.cached()
.named("class-type");
final FunctionalParser<ParseResult, PyElementType> tupleType =
op("(").skipThen(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op(")"))
@@ -154,14 +155,13 @@ public class PyTypeParser {
.named("type-parameter");
final FunctionalParser<ParseResult, PyElementType> simpleExpr =
simpleType
classType
.or(tupleType)
.or(typeParameter)
.cached()
.named("simple-expr");
final FunctionalParser<ParseResult, PyElementType> paramExpr =
simpleExpr.thenSkip(op("[")).then(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op("]"))
classType.thenSkip(op("[")).then(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op("]"))
.map(new Function<Pair<Pair<ParseResult, ParseResult>, List<ParseResult>>, ParseResult>() {
@Override
public ParseResult fun(Pair<Pair<ParseResult, ParseResult>, List<ParseResult>> value) {
@@ -187,7 +187,7 @@ public class PyTypeParser {
return EMPTY_RESULT;
}
})
.or(simpleExpr.thenSkip(op("of")).then(simpleExpr)
.or(classType.thenSkip(op("of")).then(simpleExpr)
.map(new Function<Pair<ParseResult, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<ParseResult, ParseResult> value) {
@@ -205,7 +205,7 @@ public class PyTypeParser {
return EMPTY_RESULT;
}
}))
.or(simpleExpr.thenSkip(op("from")).then(simpleExpr).thenSkip(op("to")).then(simpleExpr)
.or(classType.thenSkip(op("from")).then(simpleExpr).thenSkip(op("to")).then(simpleExpr)
.map(new Function<Pair<Pair<ParseResult, ParseResult>, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<Pair<ParseResult, ParseResult>, ParseResult> value) {
@@ -222,11 +222,45 @@ public class PyTypeParser {
return EMPTY_RESULT;
}
}))
.or(simpleExpr)
.named("param-expr");
final FunctionalParser<ParseResult, PyElementType> callableExpr =
op("(").skipThen(maybe(typeExpr.then(many(op(",").skipThen(typeExpr))))).thenSkip(op(")")).thenSkip(op("->")).then(typeExpr)
.map(
new Function<Pair<Pair<ParseResult, List<ParseResult>>, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<Pair<ParseResult, List<ParseResult>>, ParseResult> value) {
final List<PyType> parameterTypes = new ArrayList<PyType>();
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;
parameterTypes.add(first.getType());
for (ParseResult r : second) {
result = result.merge(r);
parameterTypes.add(r.getType());
}
result = result.merge(returnResult);
}
else {
result = returnResult;
}
return result.withType(new PyCallableTypeImpl(parameterTypes, returnResult.getType()));
}
})
.named("callable-expr");
final FunctionalParser<ParseResult, PyElementType> singleExpr =
paramExpr
.or(callableExpr)
.or(simpleExpr)
.named("single-expr");
final FunctionalParser<ParseResult, PyElementType> unionExpr =
paramExpr.then(many(op("or").or(op("|")).skipThen(paramExpr)))
singleExpr.then(many(op("or").or(op("|")).skipThen(singleExpr)))
.map(new Function<Pair<ParseResult, List<ParseResult>>, ParseResult>() {
@Override
public ParseResult fun(Pair<ParseResult, List<ParseResult>> value) {
@@ -227,4 +227,33 @@ public class PyTypeParserTest extends PyTestCase {
assertClassType(list.get(1), "str");
assertClassType(list.get(2), "unicode");
}
public void testCallableType() {
myFixture.configureByFile("typeParser/typeParser.py");
final PyType type = PyTypeParser.getTypeByName(myFixture.getFile(), "(int, T) -> T");
assertInstanceOf(type, PyCallableType.class);
final PyCallableType callableType = (PyCallableType)type;
assertNotNull(callableType);
final PyType returnType = callableType.getCallType(getTypeEvalContext(), null);
assertInstanceOf(returnType, PyGenericType.class);
final List<PyType> parameterTypes = callableType.getParameterTypes(getTypeEvalContext());
assertNotNull(parameterTypes);
assertEquals(2, parameterTypes.size());
assertEquals("int", parameterTypes.get(0).getName());
assertEquals("T", parameterTypes.get(1).getName());
}
public void testCallableWithoutArgs() {
myFixture.configureByFile("typeParser/typeParser.py");
final PyType type = PyTypeParser.getTypeByName(myFixture.getFile(), "() -> int");
assertInstanceOf(type, PyCallableType.class);
final PyCallableType callableType = (PyCallableType)type;
assertNotNull(callableType);
final PyType returnType = callableType.getCallType(getTypeEvalContext(), null);
assertNotNull(returnType);
assertEquals("int", returnType.getName());
final List<PyType> parameterTypes = callableType.getParameterTypes(getTypeEvalContext());
assertNotNull(parameterTypes);
assertEquals(0, parameterTypes.size());
}
}
@@ -582,6 +582,32 @@ public class PyTypeTest extends PyTestCase {
" pass\n");
}
public void testFunctionTypeAsUnificationArgument() {
doTest("int",
"def map2(f, xs):\n" +
" '''\n" +
" :type f: (T) -> V | None\n" +
" :type xs: collections.Iterable[T] | bytes | unicode\n" +
" :rtype: list[V] | bytes | unicode\n" +
" '''\n" +
" pass\n" +
"\n" +
"expr = map2(lambda x: 10, ['1', '2', '3'])[0]\n");
}
public void testFunctionTypeAsUnificationResult() {
doTest("int",
"def f(x):\n" +
" '''\n" +
" :type x: T\n" +
" :rtype: () -> T\n" +
" '''\n" +
" pass\n" +
"\n" +
"g = f(10)\n" +
"expr = g()\n");
}
private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) {
return TypeEvalContext.userInitiated(element.getContainingFile()).withTracing();
}