blob: 9e1923218494b741d7286fc70c1bef93aafcbcba [file] [log] [blame]
/*
* Copyright 2000-2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.types;
import com.google.common.collect.ImmutableMap;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.Function;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import com.jetbrains.python.psi.types.functionalParser.ForwardDeclaration;
import com.jetbrains.python.psi.types.functionalParser.FunctionalParser;
import com.jetbrains.python.psi.types.functionalParser.ParserException;
import com.jetbrains.python.psi.types.functionalParser.Token;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
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.*;
/**
* @author vlan
*/
public class PyTypeParser {
private static final ParseResult EMPTY_RESULT = new ParseResult(null, Collections.<TextRange, PyType>emptyMap(), Collections.<PyType, TextRange>emptyMap(),
Collections.<PyType, PyImportElement>emptyMap());
public static class ParseResult {
@Nullable private PyType myType;
@NotNull private Map<TextRange, ? extends PyType> myTypes;
@NotNull private Map<? extends PyType, TextRange> myFullRanges;
@NotNull private final Map<? extends PyType, PyImportElement> myImports;
ParseResult(@Nullable PyType type, @NotNull Map<TextRange, ? extends PyType> types,
@NotNull Map<? extends PyType, TextRange> fullRanges,
@NotNull Map<? extends PyType, PyImportElement> imports) {
myType = type;
myTypes = types;
myFullRanges = fullRanges;
myImports = imports;
}
ParseResult(@NotNull PyType type, @NotNull TextRange range) {
this(type, ImmutableMap.of(range, type), ImmutableMap.of(type, range), ImmutableMap.<PyType, PyImportElement>of());
}
@Nullable
public PyType getType() {
return myType;
}
@NotNull
public Map<TextRange, ? extends PyType> getTypes() {
return myTypes;
}
@NotNull
public Map<? extends PyType, TextRange> getFullRanges() {
return myFullRanges;
}
@NotNull
public Map<? extends PyType, PyImportElement> getImports() {
return myImports;
}
private ParseResult merge(@NotNull ParseResult result) {
final Map<TextRange, PyType> types = new HashMap<TextRange, PyType>();
final Map<PyType, TextRange> fullRanges = new HashMap<PyType, TextRange>();
final Map<PyType, PyImportElement> imports = new HashMap<PyType, PyImportElement>();
types.putAll(myTypes);
types.putAll(result.getTypes());
fullRanges.putAll(myFullRanges);
fullRanges.putAll(result.getFullRanges());
imports.putAll(myImports);
imports.putAll(result.getImports());
return new ParseResult(myType, types, fullRanges, imports);
}
private ParseResult withType(@Nullable PyType type) {
return new ParseResult(type, myTypes, myFullRanges, myImports);
}
}
@Nullable
public static PyType getTypeByName(@Nullable final PsiElement anchor, @NotNull String type) {
return parse(anchor, type).getType();
}
@NotNull
public static ParseResult parse(@Nullable final PsiElement anchor, @NotNull String type) {
if (anchor == null || !anchor.isValid()) {
return EMPTY_RESULT;
}
final ForwardDeclaration<ParseResult, PyElementType> typeExpr = ForwardDeclaration.create();
final FunctionalParser<ParseResult, PyElementType> classType =
token(IDENTIFIER).then(many(op(".").skipThen(token(IDENTIFIER))))
.map(new MakeSimpleType(anchor))
.cached()
.named("class-type");
final FunctionalParser<ParseResult, PyElementType> tupleType =
op("(").skipThen(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op(")"))
.map(new Function<Pair<ParseResult, List<ParseResult>>, ParseResult>() {
@Override
public ParseResult fun(Pair<ParseResult, List<ParseResult>> value) {
ParseResult result = value.getFirst();
final List<ParseResult> rest = value.getSecond();
if (rest.isEmpty()) {
return result;
}
final List<PyType> types = new ArrayList<PyType>();
types.add(result.getType());
for (ParseResult r : rest) {
result = result.merge(r);
types.add(r.getType());
}
return result.withType(PyTupleType.create(anchor, types.toArray(new PyType[types.size()])));
}
})
.named("tuple-type");
final FunctionalParser<ParseResult, PyElementType> typeParameter =
token(PARAMETER).then(maybe(op("<=").skipThen(typeExpr)))
.map(new Function<Pair<Token<PyElementType>, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<Token<PyElementType>, ParseResult> value) {
final Token<PyElementType> token = value.getFirst();
final String name = token.getText().toString();
final TextRange range = token.getRange();
final ParseResult boundResult = value.getSecond();
if (boundResult != null) {
final PyGenericType type = new PyGenericType(name, boundResult.getType());
final ParseResult result = new ParseResult(type, range);
return result.merge(boundResult).withType(type);
}
return new ParseResult(new PyGenericType(name, null), range);
}
})
.named("type-parameter");
final FunctionalParser<ParseResult, PyElementType> simpleExpr =
classType
.or(tupleType)
.or(typeParameter)
.named("simple-expr");
final FunctionalParser<ParseResult, PyElementType> paramExpr =
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) {
final Pair<ParseResult, ParseResult> firstPair = value.getFirst();
final ParseResult first = firstPair.getFirst();
final ParseResult second = firstPair.getSecond();
final List<ParseResult> third = value.getSecond();
final PyType firstType = first.getType();
if (firstType instanceof PyClassType) {
final List<PyType> tupleTypes = new ArrayList<PyType>();
tupleTypes.add(second.getType());
ParseResult result = first;
result = result.merge(second);
for (ParseResult r : third) {
tupleTypes.add(r.getType());
result = result.merge(r);
}
final PyType elementType = third.isEmpty() ? second.getType() :
PyTupleType.create(anchor, tupleTypes.toArray(new PyType[tupleTypes.size()]));
final PyCollectionTypeImpl type = new PyCollectionTypeImpl(((PyClassType)firstType).getPyClass(), false, elementType);
return result.withType(type);
}
return EMPTY_RESULT;
}
})
.or(classType.thenSkip(op("of")).then(simpleExpr)
.map(new Function<Pair<ParseResult, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<ParseResult, ParseResult> value) {
final ParseResult firstResult = value.getFirst();
final ParseResult secondResult = value.getSecond();
final ParseResult result = firstResult.merge(secondResult);
final PyType firstType = firstResult.getType();
final PyType secondType = secondResult.getType();
if (firstType != null) {
if (firstType instanceof PyClassType && secondType != null) {
return result.withType(new PyCollectionTypeImpl(((PyClassType)firstType).getPyClass(), false, secondType));
}
return result.withType(firstType);
}
return EMPTY_RESULT;
}
}))
.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) {
final Pair<ParseResult, ParseResult> firstPair = value.getFirst();
final ParseResult first = firstPair.getFirst();
final ParseResult second = firstPair.getSecond();
final ParseResult third = value.getSecond();
final PyType firstType = first.getType();
if (firstType instanceof PyClassType) {
final PyTupleType tupleType = PyTupleType.create(anchor, new PyType[]{second.getType(), third.getType()});
final PyCollectionTypeImpl type = new PyCollectionTypeImpl(((PyClassType)firstType).getPyClass(), false, tupleType);
return first.merge(second).merge(third).withType(type);
}
return EMPTY_RESULT;
}
}))
.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<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("callable-expr");
final FunctionalParser<ParseResult, PyElementType> singleExpr =
paramExpr
.or(callableExpr)
.or(simpleExpr)
.named("single-expr");
final FunctionalParser<ParseResult, PyElementType> unionExpr =
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) {
final ParseResult first = value.getFirst();
final List<ParseResult> rest = value.getSecond();
if (rest.isEmpty()) {
return first;
}
final List<PyType> types = new ArrayList<PyType>();
types.add(first.getType());
ParseResult result = first;
for (ParseResult r : rest) {
types.add(r.getType());
result = result.merge(r);
}
return result.withType(PyUnionType.union(types));
}
})
.named("union-expr");
typeExpr
.define(unionExpr)
.named("type-expr");
final FunctionalParser<ParseResult, PyElementType> typeFile =
typeExpr
.endOfInput()
.named("type-file");
try {
return typeFile.parse(tokenize(type));
}
catch (ParserException e) {
return EMPTY_RESULT;
}
}
private static class MakeSimpleType implements Function<Pair<Token<PyElementType>, List<Token<PyElementType>>>, ParseResult> {
@NotNull private final PsiElement myAnchor;
public MakeSimpleType(@NotNull PsiElement anchor) {
myAnchor = anchor;
}
@Nullable
@Override
public ParseResult fun(@NotNull Pair<Token<PyElementType>, List<Token<PyElementType>>> value) {
final Token<PyElementType> first = value.getFirst();
final List<Token<PyElementType>> rest = value.getSecond();
final TextRange firstRange = first.getRange();
final boolean unqualified = rest.isEmpty();
if (unqualified) {
final ParseResult result = parseBuiltinType(first);
if (result != null) {
return result;
}
}
final PsiFile file = myAnchor.getContainingFile();
final List<Token<PyElementType>> tokens = new ArrayList<Token<PyElementType>>();
tokens.add(first);
tokens.addAll(rest);
if (file instanceof PyFile) {
final PyFile pyFile = (PyFile)file;
final TypeEvalContext context = TypeEvalContext.codeInsightFallback();
final Map<TextRange, PyType> types = new HashMap<TextRange, PyType>();
final Map<PyType, TextRange> fullRanges = new HashMap<PyType, TextRange>();
final Map<PyType, PyImportElement> imports = new HashMap<PyType, PyImportElement>();
PyType type = resolveQualifierType(tokens, pyFile, context, types, fullRanges, imports);
if (type != null) {
final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(context);
final PyExpression expression = myAnchor instanceof PyExpression ? (PyExpression)myAnchor : null;
for (Token<PyElementType> token : tokens) {
final PyType qualifierType = type;
type = null;
final List<? extends RatedResolveResult> results = qualifierType.resolveMember(token.getText().toString(), expression,
AccessDirection.READ, resolveContext);
if (results != null && !results.isEmpty()) {
final PsiElement resolved = results.get(0).getElement();
if (resolved instanceof PyTypedElement) {
type = context.getType((PyTypedElement)resolved);
if (type != null && !allowResolveToType(type)) {
type = null;
break;
}
if (type instanceof PyClassLikeType) {
type = ((PyClassLikeType)type).toInstance();
}
}
}
if (type == null) {
break;
}
types.put(token.getRange(), type);
fullRanges.put(type, TextRange.create(firstRange.getStartOffset(), token.getRange().getEndOffset()));
}
if (type != null) {
return new ParseResult(type, types, fullRanges, imports);
}
}
}
if (unqualified) {
final ParseResult result = fromClassNameIndex(first);
if (result != null) {
return result;
}
}
return EMPTY_RESULT;
}
@Nullable
private PyType resolveQualifierType(@NotNull List<Token<PyElementType>> tokens,
@NotNull PyFile file,
@NotNull TypeEvalContext context,
@NotNull Map<TextRange, PyType> types,
@NotNull Map<PyType, TextRange> fullRanges,
@NotNull Map<PyType, PyImportElement> imports) {
if (tokens.isEmpty()) {
return null;
}
final Token<PyElementType> firstToken = tokens.get(0);
final String firstText = firstToken.getText().toString();
final TextRange firstRange = firstToken.getRange();
PyType type = null;
PsiElement resolved = file.getElementNamed(firstText);
if (resolved != null) {
// Local or imported name
if (resolved instanceof PyTypedElement) {
type = context.getType((PyTypedElement)resolved);
if (type != null) {
tokens.remove(0);
if (!allowResolveToType(type)) {
return null;
}
if (type instanceof PyClassLikeType) {
type = ((PyClassLikeType)type).toInstance();
}
types.put(firstRange, type);
fullRanges.put(type, firstRange);
for (PyFromImportStatement fromImportStatement : file.getFromImports()) {
for (PyImportElement importElement : fromImportStatement.getImportElements()) {
if (firstText.equals(importElement.getVisibleName())) {
imports.put(type, importElement);
}
}
}
for (PyImportElement importElement : file.getImportTargets()) {
if (firstText.equals(importElement.getVisibleName())) {
imports.put(type, importElement);
}
}
}
}
}
else {
// Implicitly available in the type string
QualifiedName qName = null;
while (!tokens.isEmpty()) {
final Token<PyElementType> token = tokens.get(0);
final String name = token.getText().toString();
qName = qName != null ? qName.append(name) : QualifiedName.fromComponents(name);
PsiElement module = new QualifiedNameResolverImpl(qName).fromElement(myAnchor).firstResult();
if (module == null) {
break;
}
if (module instanceof PsiDirectory) {
module = PyUtil.getPackageElement((PsiDirectory)module, myAnchor);
}
if (module instanceof PyTypedElement) {
final PyType moduleType = context.getType((PyTypedElement)module);
if (moduleType != null) {
type = moduleType;
types.put(token.getRange(), type);
fullRanges.put(type, TextRange.create(firstRange.getStartOffset(), token.getRange().getEndOffset()));
}
}
tokens.remove(0);
}
}
return type;
}
private static boolean allowResolveToType(@NotNull PyType type) {
return type instanceof PyClassLikeType || type instanceof PyModuleType || type instanceof PyImportedModuleType;
}
@Nullable
private ParseResult parseBuiltinType(@NotNull Token<PyElementType> token) {
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(myAnchor);
final String name = token.getText().toString();
final TextRange range = token.getRange();
if (PyNames.UNKNOWN_TYPE.equals(name)) {
return EMPTY_RESULT;
}
else if (PyNames.NONE.equals(name)) {
return new ParseResult(PyNoneType.INSTANCE, range);
}
else if ("integer".equals(name) || ("long".equals(name) && LanguageLevel.forElement(myAnchor).isPy3K())) {
final PyClassType type = builtinCache.getIntType();
return type != null ? new ParseResult(type, range) : EMPTY_RESULT;
}
else if ("string".equals(name)) {
final PyType type = builtinCache.getStringType(LanguageLevel.forElement(myAnchor));
return type != null ? new ParseResult(type, range) : EMPTY_RESULT;
}
else if ("bytestring".equals(name)) {
final PyType type = builtinCache.getByteStringType(LanguageLevel.forElement(myAnchor));
return type != null ? new ParseResult(type, range) : EMPTY_RESULT;
}
else if ("bytes".equals(name)) {
final PyClassType type = builtinCache.getBytesType(LanguageLevel.forElement(myAnchor));
return type != null ? new ParseResult(type, range) : EMPTY_RESULT;
}
else if ("unicode".equals(name)) {
final PyClassType type = builtinCache.getUnicodeType(LanguageLevel.forElement(myAnchor));
return type != null ? new ParseResult(type, range) : EMPTY_RESULT;
}
else if ("boolean".equals(name)) {
final PyClassType type = builtinCache.getBoolType();
return type != null ? new ParseResult(type, range) : EMPTY_RESULT;
}
else if ("dictionary".equals(name)) {
final PyClassType type = builtinCache.getDictType();
return type != null ? new ParseResult(type, range) : EMPTY_RESULT;
}
final PyType builtinType = builtinCache.getObjectType(name);
if (builtinType != null) {
return new ParseResult(builtinType, range);
}
return null;
}
@Nullable
private ParseResult fromClassNameIndex(@NotNull Token<PyElementType> token) {
final Collection<PyClass> classes = PyClassNameIndex.find(token.getText().toString(), myAnchor.getProject(), true);
if (classes.size() == 1) {
return parseResultFromClass(token, classes.iterator().next());
}
for (PyClass cls : classes) {
final PsiFile file = cls.getContainingFile();
if (file != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(file)) {
return parseResultFromClass(token, cls);
}
}
return null;
}
@NotNull
private static ParseResult parseResultFromClass(@NotNull Token<PyElementType> token, @NotNull PyClass cls) {
final PyClassTypeImpl type = new PyClassTypeImpl(cls, false);
type.assertValid("PyClassNameIndex.find().iterator()");
return new ParseResult(type, token.getRange());
}
}
private static FunctionalParser<Token<PyElementType>, PyElementType> op(@Nullable String text) {
return token(PyTypeTokenTypes.OP, text);
}
private static List<Token<PyElementType>> tokenize(@NotNull String s) {
final List<Token<PyElementType>> tokens = new ArrayList<Token<PyElementType>>();
final PyTypeLexer lexer = new PyTypeLexer(new StringReader(s));
lexer.reset(s, 0, s.length(), lexer.yystate());
try {
PyElementType type;
while ((type = lexer.advance()) != null) {
if (type == PyTypeTokenTypes.SPACE || type == PyTypeTokenTypes.MARKUP) {
continue;
}
final TextRange range = TextRange.create(lexer.getTokenStart(), lexer.getTokenEnd());
final Token<PyElementType> token = new Token<PyElementType>(type, lexer.yytext(), range);
tokens.add(token);
}
}
catch (IOException e) {
return Collections.emptyList();
}
catch (Error e) {
return Collections.emptyList();
}
return tokens;
}
}