PY-18816 Type comments for target expressions are stored inside their stubs

This commit is contained in:
Mikhail Golubev
2016-03-17 18:40:29 +03:00
committed by Andrey Vlasovskikh
parent 57bbfd50d4
commit a68e6981dc
9 changed files with 97 additions and 43 deletions
@@ -27,7 +27,7 @@ import org.jetbrains.annotations.Nullable;
*/
public interface PyTargetExpression extends PyQualifiedExpression, PsiNamedElement, PsiNameIdentifierOwner, PyDocStringOwner,
PyQualifiedNameOwner, PyReferenceOwner, StubBasedPsiElement<PyTargetExpressionStub>,
PyPossibleClassMember {
PyPossibleClassMember, PyTypeCommentOwner {
PyTargetExpression[] EMPTY_ARRAY = new PyTargetExpression[0];
/**
@@ -63,4 +63,7 @@ public interface PyTargetExpressionStub extends NamedStub<PyTargetExpression> {
@Nullable
String getDocString();
@Nullable
String getTypeComment();
}
@@ -21,7 +21,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.util.PsiTreeUtil;
@@ -173,9 +172,9 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
@Override
public PyType getReferenceType(@NotNull PsiElement referenceTarget, TypeEvalContext context, @Nullable PsiElement anchor) {
if (referenceTarget instanceof PyTargetExpression && context.maySwitchToAST(referenceTarget)) {
if (referenceTarget instanceof PyTargetExpression) {
final PyTargetExpression target = (PyTargetExpression)referenceTarget;
final String comment = getTypeComment(target);
final String comment = target.getTypeCommentAnnotation();
if (comment != null) {
final PyType type = getStringBasedType(comment, referenceTarget, new Context(context));
if (type instanceof PyTupleType) {
@@ -190,20 +189,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
return null;
}
@Nullable
private static String getTypeComment(@NotNull PyTargetExpression target) {
final PsiElement commentContainer = PsiTreeUtil.getParentOfType(target, PyAssignmentStatement.class, PyWithStatement.class,
PyForPart.class);
if (commentContainer != null) {
final PsiComment comment = getSameLineTrailingCommentChild(commentContainer);
if (comment != null) {
final String text = comment.getText();
return getTypeCommentValue(text);
}
}
return null;
}
/**
* Checks that text of a comment starts with the "type:" prefix and returns trimmed part afterwards. This trailing part is supposed to
* contain type annotation in PEP 484 compatible format, that can be parsed with either {@link PyTypeParser#parse(PsiElement, String)}
@@ -218,23 +203,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
return null;
}
@Nullable
private static PsiComment getSameLineTrailingCommentChild(@NotNull PsiElement element) {
PsiElement child = element.getFirstChild();
while (true) {
if (child == null) {
return null;
}
if (child instanceof PsiComment) {
return (PsiComment)child;
}
if (child.getText().contains("\n")) {
return null;
}
child = child.getNextSibling();
}
}
private static boolean isAny(@NotNull PyType type) {
return type instanceof PyClassType && "typing.Any".equals(((PyClassType)type).getPyClass().getQualifiedName());
}
@@ -62,7 +62,7 @@ public class PyFileElementType extends IStubFileElementType<PyFileStub> {
@Override
public int getStubVersion() {
// Don't forget to update versions of indexes that use the updated stub-based elements
return 54;
return 55;
}
@Nullable
@@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.PsiReference;
@@ -35,6 +36,7 @@ import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.codeInsight.PyTypingTypeProvider;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
@@ -677,4 +679,52 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl<PyTargetExpression
public String getQualifiedName() {
return QualifiedNameFinder.getQualifiedName(this);
}
@Nullable
@Override
public PsiComment getTypeComment() {
final PsiElement commentContainer = PsiTreeUtil.getParentOfType(this,
PyAssignmentStatement.class,
PyWithStatement.class,
PyForPart.class);
if (commentContainer != null) {
final PsiComment comment = getSameLineTrailingCommentChild(commentContainer);
if (comment != null && PyTypingTypeProvider.getTypeCommentValue(comment.getText()) != null) {
return comment;
}
}
return null;
}
@Nullable
private static PsiComment getSameLineTrailingCommentChild(@NotNull PsiElement element) {
PsiElement child = element.getFirstChild();
while (true) {
if (child == null) {
return null;
}
if (child instanceof PsiComment) {
return (PsiComment)child;
}
if (child.getText().contains("\n")) {
return null;
}
child = child.getNextSibling();
}
}
@Nullable
@Override
public String getTypeCommentAnnotation() {
final PyTargetExpressionStub stub = getStub();
if (stub != null) {
return stub.getTypeComment();
}
final PsiComment comment = getTypeComment();
if (comment != null) {
return PyTypingTypeProvider.getTypeCommentValue(comment.getText());
}
return null;
}
}
@@ -70,10 +70,11 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
final String name = psi.getName();
final PyExpression assignedValue = psi.findAssignedValue();
final String docString = DocStringUtil.getDocStringValue(psi);
final String typeComment = psi.getTypeCommentAnnotation();
for (CustomTargetExpressionStubType customStubType : getCustomStubTypes()) {
CustomTargetExpressionStub customStub = customStubType.createStub(psi);
if (customStub != null) {
return new PyTargetExpressionStubImpl(name, docString, customStub, parentStub);
return new PyTargetExpressionStubImpl(name, docString, typeComment, customStub, parentStub);
}
}
PyTargetExpressionStub.InitializerType initializerType = PyTargetExpressionStub.InitializerType.Other;
@@ -89,7 +90,7 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
initializer = ((PyReferenceExpression) callee).asQualifiedName();
}
}
return new PyTargetExpressionStubImpl(name, docString, initializerType, initializer, psi.isQualified(), parentStub);
return new PyTargetExpressionStubImpl(name, docString, initializerType, initializer, psi.isQualified(), typeComment, parentStub);
}
public void serialize(@NotNull final PyTargetExpressionStub stub, @NotNull final StubOutputStream stream)
@@ -98,6 +99,7 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
final String docString = stub.getDocString();
stream.writeUTFFast(docString != null ? docString : "");
stream.writeVarInt(stub.getInitializerType().getIndex());
stream.writeName(stub.getTypeComment());
final CustomTargetExpressionStub customStub = stub.getCustomStub(CustomTargetExpressionStub.class);
if (customStub != null) {
stream.writeName(customStub.getTypeClass().getCanonicalName());
@@ -118,19 +120,21 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
docString = null;
}
PyTargetExpressionStub.InitializerType initializerType = PyTargetExpressionStub.InitializerType.fromIndex(stream.readVarInt());
final StringRef typeCommentRef = stream.readName();
final String typeComment = typeCommentRef == null ? null : typeCommentRef.getString();
if (initializerType == PyTargetExpressionStub.InitializerType.Custom) {
final String typeName = stream.readName().getString();
for(CustomTargetExpressionStubType type: getCustomStubTypes()) {
if (type.getClass().getCanonicalName().equals(typeName)) {
CustomTargetExpressionStub stub = type.deserializeStub(stream);
return new PyTargetExpressionStubImpl(name, docString, stub, parentStub);
return new PyTargetExpressionStubImpl(name, docString, typeComment, stub, parentStub);
}
}
throw new IOException("Unknown custom stub type " + typeName);
}
QualifiedName initializer = QualifiedName.deserialize(stream);
boolean isQualified = stream.readBoolean();
return new PyTargetExpressionStubImpl(name, docString, initializerType, initializer, isQualified, parentStub);
return new PyTargetExpressionStubImpl(name, docString, initializerType, initializer, isQualified, typeComment, parentStub);
}
public boolean shouldCreateStub(final ASTNode node) {
@@ -31,16 +31,19 @@ public class PyTargetExpressionStubImpl extends StubBase<PyTargetExpression> imp
private final InitializerType myInitializerType;
private final QualifiedName myInitializer;
private final boolean myQualified;
@Nullable private final String myDocString;
private final String myTypeComment;
@Nullable private final String myDocString;
private final CustomTargetExpressionStub myCustomStub;
public PyTargetExpressionStubImpl(String name,
@Nullable String docString,
@Nullable String typeComment,
CustomTargetExpressionStub customStub,
StubElement parent) {
super(parent, PyElementTypes.TARGET_EXPRESSION);
myName = name;
myTypeComment = typeComment;
myInitializerType = InitializerType.Custom;
myInitializer = null;
myQualified = false;
@@ -48,12 +51,16 @@ public class PyTargetExpressionStubImpl extends StubBase<PyTargetExpression> imp
myDocString = docString;
}
public PyTargetExpressionStubImpl(final String name, @Nullable String docString, final InitializerType initializerType,
public PyTargetExpressionStubImpl(final String name,
@Nullable String docString,
final InitializerType initializerType,
final QualifiedName initializer,
final boolean qualified,
@Nullable String typeComment,
final StubElement parentStub) {
super(parentStub, PyElementTypes.TARGET_EXPRESSION);
myName = name;
myTypeComment = typeComment;
assert initializerType != InitializerType.Custom;
myInitializerType = initializerType;
myInitializer = initializer;
@@ -94,6 +101,12 @@ public class PyTargetExpressionStubImpl extends StubBase<PyTargetExpression> imp
return myDocString;
}
@Nullable
@Override
public String getTypeComment() {
return myTypeComment;
}
@Override
public String toString() {
return "PyTargetExpressionStub(name=" + myName + ")";
@@ -0,0 +1 @@
x = unknown() # type: int
@@ -445,9 +445,24 @@ public class PyStubsTest extends PyTestCase {
assertEquals("int", annotation);
assertNotParsed(file);
final TypeEvalContext context = TypeEvalContext.codeInsightFallback(myFixture.getProject());
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), file);
final PyType paramType = context.getType(param);
assertInstanceOf(paramType, PyClassType.class);
assertNotParsed(file);
}
public void testTargetExpressionTypeComment() {
final PyFile file = getTestFile();
final PyTargetExpression target = file.findTopLevelAttribute("x");
assertNotNull(target);
final String annotation = target.getTypeCommentAnnotation();
assertEquals("int", annotation);
assertNotParsed(file);
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), file);
final PyType paramType = context.getType(target);
assertInstanceOf(paramType, PyClassType.class);
assertNotParsed(file);
}
}