PY-34617 Support version check

GitOrigin-RevId: 3318ff79cdcc5ba0ce5e4feb65abad5ad0f4acfa
This commit is contained in:
Petr
2024-07-28 00:24:15 +00:00
committed by intellij-monorepo-bot
parent 16a7fb4b3e
commit 93b9066edf
60 changed files with 1086 additions and 219 deletions
@@ -11,17 +11,18 @@ if sys.version_info < (3, 10):
from _collections_abc import *
else:
from collections.abc import (
Callable,
ItemsView,
Iterable,
Iterator,
KeysView,
Mapping,
MutableMapping,
MutableSequence,
Reversible,
Sequence,
ValuesView,
Callable as Callable,
Hashable as Hashable,
ItemsView as ItemsView,
Iterable as Iterable,
Iterator as Iterator,
KeysView as KeysView,
Mapping as Mapping,
MutableMapping as MutableMapping,
MutableSequence as MutableSequence,
Reversible as Reversible,
Sequence as Sequence,
ValuesView as ValuesView,
)
__all__ = ["ChainMap", "Counter", "OrderedDict", "UserDict", "UserList", "UserString", "defaultdict", "deque", "namedtuple"]
@@ -381,6 +381,7 @@ else:
def __ror__(self, other: Any) -> _SpecialForm: ...
if sys.version_info >= (3, 12):
from collections.abc import Buffer as Buffer
from types import get_original_bases as get_original_bases
from typing import TypeAliasType as TypeAliasType, override as override
else:
@@ -2,7 +2,10 @@
package com.jetbrains.python.ast;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNameIdentifierOwner;
import com.intellij.psi.TokenType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayFactory;
import com.intellij.util.ArrayUtil;
@@ -11,9 +14,9 @@ import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.ast.impl.PyUtilCore;
import com.jetbrains.python.ast.controlFlow.AstScopeOwner;
import com.jetbrains.python.ast.docstring.DocStringUtilCore;
import com.jetbrains.python.ast.impl.PyUtilCore;
import com.jetbrains.python.psi.LanguageLevel;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -28,7 +31,7 @@ import java.util.Optional;
@ApiStatus.Experimental
public interface PyAstFunction extends PsiNameIdentifierOwner, PyAstCompoundStatement,
PyAstDecoratable, PyAstCallable, PyAstStatementListContainer, PyAstPossibleClassMember,
AstScopeOwner, PyAstDocStringOwner, PyAstTypeCommentOwner, PyAstAnnotationOwner, PyAstTypeParameterListOwner{
AstScopeOwner, PyAstDocStringOwner, PyAstTypeCommentOwner, PyAstAnnotationOwner, PyAstTypeParameterListOwner {
PyAstFunction[] EMPTY_ARRAY = new PyAstFunction[0];
ArrayFactory<PyAstFunction> ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new PyAstFunction[count];
@@ -231,7 +234,7 @@ public interface PyAstFunction extends PsiNameIdentifierOwner, PyAstCompoundStat
@Override
@Nullable
default PyAstClass getContainingClass() {
final PsiElement parent = PsiTreeUtil.getParentOfType(this, StubBasedPsiElement.class);
final PsiElement parent = PsiTreeUtil.getParentOfType(this, AstScopeOwner.class);
if (parent instanceof PyAstClass) {
return (PyAstClass)parent;
}
@@ -17,7 +17,6 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -80,43 +79,6 @@ public final class PyPsiUtilsCore {
return null;
}
@ApiStatus.Internal
@NotNull
public static <T extends PyAstElement> List<T> collectChildren(@NotNull PyAstFile pyFile, @NotNull Class<T> elementType) {
final List<T> result = new ArrayList<>();
pyFile.acceptChildren(new TopLevelVisitor() {
@Override
protected void checkAddElement(PsiElement node) {
if (elementType.isInstance(node)) {
result.add(elementType.cast(node));
}
}
@Override
public void visitPyStatement(@NotNull PyAstStatement node) {
if (PyAstStatement.class.isAssignableFrom(elementType) && !(node instanceof PyAstCompoundStatement)) {
checkAddElement(node);
return;
}
super.visitPyStatement(node);
}
});
return result;
}
@ApiStatus.Internal
@NotNull
public static List<PsiElement> collectAllChildren(PsiElement e) {
final List<PsiElement> result = new ArrayList<>();
e.acceptChildren(new TopLevelVisitor() {
@Override
protected void checkAddElement(PsiElement node) {
result.add(node);
}
});
return result;
}
/**
* Returns the first non-whitespace sibling following the given element but within its line boundaries.
*/
@@ -135,26 +97,6 @@ public final class PyPsiUtilsCore {
return null;
}
private static abstract class TopLevelVisitor extends PyAstRecursiveElementVisitor {
@Override
public void visitPyElement(final @NotNull PyAstElement node) {
super.visitPyElement(node);
checkAddElement(node);
}
@Override
public void visitPyClass(final @NotNull PyAstClass node) {
checkAddElement(node); // do not recurse into functions
}
@Override
public void visitPyFunction(final @NotNull PyAstFunction node) {
checkAddElement(node); // do not recurse into classes
}
protected abstract void checkAddElement(PsiElement node);
}
@Nullable
public static String strValue(@Nullable PyAstExpression expression) {
return expression instanceof PyAstStringLiteralExpression ? ((PyAstStringLiteralExpression)expression).getStringValue() : null;
@@ -198,4 +140,12 @@ public final class PyPsiUtilsCore {
}
PsiUtilCore.ensureValid(element);
}
@Nullable
public static PyAstExpression flattenParens(@Nullable PyAstExpression expr) {
while (expr instanceof PyAstParenthesizedExpression) {
expr = ((PyAstParenthesizedExpression)expr).getContainedExpression();
}
return expr;
}
}
@@ -26,7 +26,6 @@ import java.util.regex.Pattern;
/**
* Assorted utility methods for Python code insight.
*
* These methods don't depend on the Python runtime.
*
* @see PyPsiUtilsCore for utilities used in Python PSI API
@@ -99,11 +98,13 @@ public final class PyUtilCore {
public static boolean isTopLevel(@NotNull PsiElement element) {
if (element instanceof StubBasedPsiElement) {
final StubElement stub = ((StubBasedPsiElement<?>)element).getStub();
final StubElement<?> stub = ((StubBasedPsiElement<?>)element).getStub();
if (stub != null) {
final StubElement parentStub = stub.getParentStub();
if (parentStub != null) {
return parentStub.getPsi() instanceof PsiFile;
for (StubElement<?> parentStub = stub.getParentStub(); parentStub != null; parentStub = parentStub.getParentStub()) {
PsiElement psi = parentStub.getPsi();
if (!(psi instanceof PyAstIfPart || psi instanceof PyAstElsePart)) {
return psi instanceof PsiFile;
}
}
}
}
@@ -1918,4 +1918,80 @@ public abstract class PyCommonResolveTest extends PyCommonResolveTestCase {
PyFunction containingMethod = assertInstanceOf(ScopeUtil.getScopeOwner(classAttr), PyFunction.class);
assertEquals("next", containingMethod.getName());
}
// PY-34617
public void testFileAttributeMatchingVersionCheck() {
myFixture.copyDirectoryToProject("resolve/FileAttributeUnderVersionCheck", "");
runWithLanguageLevel(LanguageLevel.PYTHON310, () -> {
myFixture.configureByText(
PythonFileType.INSTANCE,
"""
import mod
mod.foo
<ref>"""
);
final PsiElement element = PyCommonResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve();
assertResolveResult(element, PyTargetExpression.class, "foo", "mod.py");
});
assertFilesNotParsed();
}
// PY-34617
public void testFileAttributeNotMatchingVersionCheck() {
myFixture.copyDirectoryToProject("resolve/FileAttributeUnderVersionCheck", "");
runWithLanguageLevel(LanguageLevel.PYTHON310, () -> {
myFixture.configureByText(
PythonFileType.INSTANCE,
"""
import mod
mod.bar
<ref>"""
);
final PsiElement element = PyCommonResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve();
assertNull(element);
});
assertFilesNotParsed();
}
// PY-34617
public void testClassAttributeMatchingVersionCheck() {
myFixture.copyDirectoryToProject("resolve/ClassAttributeUnderVersionCheck", "");
runWithLanguageLevel(LanguageLevel.PYTHON27, () -> {
myFixture.configureByText(
PythonFileType.INSTANCE,
"""
from mod import MyClass
m = MyClass()
m.buz()
<ref>"""
);
final PsiElement element = PyCommonResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve();
assertResolveResult(element, PyFunction.class, "buz", "mod.py");
});
assertFilesNotParsed();
}
// PY-34617
public void testClassAttributeNotMatchingVersionCheck() {
myFixture.copyDirectoryToProject("resolve/ClassAttributeUnderVersionCheck", "");
runWithLanguageLevel(LanguageLevel.PYTHON27, () -> {
myFixture.configureByText(
PythonFileType.INSTANCE,
"""
from mod import MyClass
m = MyClass()
m.foo()
<ref>"""
);
final PsiElement element = PyCommonResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve();
assertNull(element);
});
assertFilesNotParsed();
}
private void assertFilesNotParsed() {
final PsiFile file = myFixture.getFile();
assertProjectFilesNotParsed(file);
assertSdkRootsNotParsed(file);
}
}
@@ -2101,6 +2101,33 @@ public abstract class PythonCommonCompletionTest extends PythonCommonTestCase {
doTest();
}
// PY-34617
public void testVersionCheckAtFileLevel() {
runWithLanguageLevel(LanguageLevel.PYTHON27, () -> {
List<String> suggested = doTestByFile();
assertContainsElements(suggested, "attr0", "attr3", "f0", "f3", "MyClass0", "MyClass3");
assertDoesntContain(suggested, "attr1", "attr2", "f1", "f2", "MyClass1", "MyClass2");
});
}
// PY-34617
public void testVersionCheckAtClassLevel() {
runWithLanguageLevel(LanguageLevel.PYTHON25, () -> {
List<String> suggested = doTestByFile();
assertContainsElements(suggested, "attr0", "attr2", "f0", "f2", "MyClass0", "MyClass2");
assertDoesntContain(suggested, "attr1", "attr3", "f1", "f3", "MyClass1", "MyClass3");
});
}
// PY-34617
public void testVersionCheckInClassInsideMethod() {
runWithLanguageLevel(LanguageLevel.PYTHON310, () -> {
List<String> suggested = doTestByFile();
assertContainsElements(suggested, "f0", "f1");
assertDoesntContain(suggested, "f2", "f3");
});
}
private void doTestHasattrContributor(String[] inList, String[] notInList) {
doTestHasattrContributor("hasattrCompletion/" + getTestName(true) + ".py", inList, notInList);
}
@@ -100,8 +100,8 @@ public interface PyElementTypes {
TokenSet STATEMENT_LISTS = TokenSet.create(STATEMENT_LIST);
// Parts
PyElementType IF_PART_IF = new PyElementType("IF_IF", Companion.getINSTANCE().getIfPartIfConstructor());
PyElementType IF_PART_ELIF = new PyElementType("IF_ELIF", Companion.getINSTANCE().getIfPartElifConstructor());
IElementType IF_PART_IF = Companion.getINSTANCE().getIfPartIf();
IElementType IF_PART_ELIF = Companion.getINSTANCE().getIfPartElif();
PyElementType FOR_PART = new PyElementType("FOR_PART", Companion.getINSTANCE().getForPartConstructor());
PyElementType WHILE_PART = new PyElementType("WHILE_PART", Companion.getINSTANCE().getWhilePartConstructor());
@@ -109,7 +109,7 @@ public interface PyElementTypes {
PyElementType TRY_PART = new PyElementType("TRY_PART", Companion.getINSTANCE().getTryPartConstructor());
PyElementType FINALLY_PART = new PyElementType("FINALLY_PART", Companion.getINSTANCE().getFinallyPartConstructor());
PyElementType ELSE_PART = new PyElementType("ELSE_PART", Companion.getINSTANCE().getElsePartConstructor());
IElementType ELSE_PART = Companion.getINSTANCE().getElsePart();
TokenSet PARTS = TokenSet.create(IF_PART_IF, IF_PART_ELIF, FOR_PART, WHILE_PART, TRY_PART, FINALLY_PART, ELSE_PART, EXCEPT_PART);
TokenSet ELIFS = TokenSet.create(IF_PART_ELIF);
@@ -31,6 +31,9 @@ abstract class PyElementTypesFacade {
abstract val typeParameter: IElementType
abstract val typeParameterList: IElementType
abstract val typeAliasStatement: IElementType
abstract val ifPartIf: IElementType
abstract val ifPartElif: IElementType
abstract val elsePart: IElementType
// reparseable elements
abstract val statementList: IElementType
@@ -94,13 +97,10 @@ abstract class PyElementTypesFacade {
abstract val setLiteralExpressionConstructor: Function<in ASTNode, out PsiElement>
abstract val setCompExpressionConstructor: Function<in ASTNode, out PsiElement>
abstract val dictCompExpressionConstructor: Function<in ASTNode, out PsiElement>
abstract val ifPartIfConstructor: Function<in ASTNode, out PsiElement>
abstract val ifPartElifConstructor: Function<in ASTNode, out PsiElement>
abstract val forPartConstructor: Function<in ASTNode, out PsiElement>
abstract val whilePartConstructor: Function<in ASTNode, out PsiElement>
abstract val tryPartConstructor: Function<in ASTNode, out PsiElement>
abstract val finallyPartConstructor: Function<in ASTNode, out PsiElement>
abstract val elsePartConstructor: Function<in ASTNode, out PsiElement>
abstract val fStringNodeConstructor: Function<in ASTNode, out PsiElement>
abstract val fStringFragmentConstructor: Function<in ASTNode, out PsiElement>
abstract val fStringFragmentFormatPartConstructor: Function<in ASTNode, out PsiElement>
@@ -17,5 +17,6 @@
<orderEntry type="module" module-name="intellij.python.parser" exported="" />
<orderEntry type="module" module-name="intellij.python.ast" exported="" />
<orderEntry type="module" module-name="intellij.python.syntax.core" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
</component>
</module>
@@ -15,10 +15,12 @@
*/
package com.jetbrains.python.psi;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.ast.PyAstElsePart;
import com.jetbrains.python.psi.stubs.PyElsePartStub;
/**
* The 'else:' part of various compound statements.
*/
public interface PyElsePart extends PyAstElsePart, PyStatementPart {
public interface PyElsePart extends PyAstElsePart, PyStatementPart, StubBasedPsiElement<PyElsePartStub> {
}
@@ -1,7 +1,9 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.ast.PyAstIfPartElif;
import com.jetbrains.python.psi.stubs.PyIfPartElifStub;
public interface PyIfPartElif extends PyAstIfPartElif, PyIfPart {
public interface PyIfPartElif extends PyAstIfPartElif, PyIfPart, StubBasedPsiElement<PyIfPartElifStub> {
}
@@ -1,7 +1,9 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.ast.PyAstIfPartIf;
import com.jetbrains.python.psi.stubs.PyIfPartIfStub;
public interface PyIfPartIf extends PyAstIfPartIf, PyIfPart {
public interface PyIfPartIf extends PyAstIfPartIf, PyIfPart, StubBasedPsiElement<PyIfPartIfStub> {
}
@@ -16,10 +16,15 @@ import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.ast.impl.PyPsiUtilsCore;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.stubs.PyElsePartStub;
import com.jetbrains.python.psi.stubs.PyIfPartElifStub;
import com.jetbrains.python.psi.stubs.PyIfPartIfStub;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -408,40 +413,6 @@ public final class PyPsiUtils {
return blocks;
}
@NotNull
static <T extends PyElement> List<T> collectStubChildren(@NotNull PyFile pyFile,
@Nullable StubElement<?> stub,
@NotNull Class<T> elementType) {
if (stub != null) {
final List<T> result = new ArrayList<>();
final List<StubElement<?>> children = stub.getChildrenStubs();
for (StubElement<?> child : children) {
PsiElement childPsi = child.getPsi();
if (elementType.isInstance(childPsi)) {
result.add(elementType.cast(childPsi));
}
}
return result;
}
else {
return PyPsiUtilsCore.collectChildren(pyFile, elementType);
}
}
static List<PsiElement> collectAllStubChildren(PsiElement e, StubElement<?> stub) {
if (stub != null) {
final List<PsiElement> result = new ArrayList<>();
final List<StubElement<?>> children = stub.getChildrenStubs();
for (StubElement<?> child : children) {
result.add(child.getPsi());
}
return result;
}
else {
return PyPsiUtilsCore.collectAllChildren(e);
}
}
public static int findArgumentIndex(PyCallExpression call, PsiElement argument) {
final PyExpression[] args = call.getArguments();
for (int i = 0; i < args.length; i++) {
@@ -504,10 +475,7 @@ public final class PyPsiUtils {
@Nullable
public static PyExpression flattenParens(@Nullable PyExpression expr) {
while (expr instanceof PyParenthesizedExpression) {
expr = ((PyParenthesizedExpression)expr).getContainedExpression();
}
return expr;
return (PyExpression)PyPsiUtilsCore.flattenParens(expr);
}
@Nullable
@@ -664,4 +632,59 @@ public final class PyPsiUtils {
while (next != null);
return null;
}
/**
* @see <a href="https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks">Version and Platform Checks</a>
*/
@ApiStatus.Internal
@Nullable
public static StubElement<?> getParentStubSkippingVersionChecks(@Nullable StubElement<?> stub) {
if (stub != null) {
for (StubElement<?> e = stub.getParentStub(); e != null; e = e.getParentStub()) {
boolean isVersionCheck = e instanceof PyIfPartIfStub || e instanceof PyIfPartElifStub || e instanceof PyElsePartStub;
if (!isVersionCheck) {
return e;
}
}
}
return null;
}
/**
* @see <a href="https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks">Version and Platform Checks</a>
*/
@ApiStatus.Internal
public static boolean processChildrenStubs(@NotNull StubElement<?> stub,
@NotNull LanguageLevel languageLevel,
@NotNull Processor<? super StubElement<?>> processor) {
boolean recurse = false;
for (StubElement<?> child : stub.getChildrenStubs()) {
if (child instanceof PyIfPartIfStub ifStub) {
if (ifStub.getVersionCheck().matches(languageLevel)) {
recurse = false;
if (!processChildrenStubs(child, languageLevel, processor)) return false;
}
else {
recurse = true;
}
}
else if (child instanceof PyIfPartElifStub elifStub) {
if (recurse && elifStub.getVersionCheck().matches(languageLevel)) {
recurse = false;
if (!processChildrenStubs(child, languageLevel, processor)) return false;
}
}
else if (child instanceof PyElsePartStub) {
if (recurse) {
recurse = false;
if (!processChildrenStubs(child, languageLevel, processor)) return false;
}
}
else {
recurse = false;
if (!processor.process(child)) return false;
}
}
return true;
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.impl
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.psi.PyIfStatement
import com.jetbrains.python.psi.PyRecursiveElementVisitor
import org.jetbrains.annotations.ApiStatus
/**
* @see [Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html.version-and-platform-checks)
*/
@ApiStatus.Internal
open class PyVersionAwareElementVisitor(private val languageLevel: LanguageLevel?) : PyRecursiveElementVisitor() {
override fun visitPyIfStatement(node: PyIfStatement) {
if (languageLevel == null) {
super.visitPyIfStatement(node)
return
}
val ifParts = sequenceOf(node.getIfPart()) + node.elifParts.asSequence()
for (ifPart in ifParts) {
val versionCheck = PyVersionCheck.fromCondition(ifPart)
if (versionCheck == null) {
super.visitPyIfStatement(node)
return
}
if (versionCheck.matches(languageLevel)) {
ifPart.statementList.accept(this)
return
}
}
node.elsePart?.statementList?.accept(this)
}
}
@@ -0,0 +1,27 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.impl
import com.intellij.psi.PsiElement
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyElement
import com.jetbrains.python.psi.PyFunction
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
abstract class PyVersionAwareTopLevelElementVisitor(languageLevel: LanguageLevel) : PyVersionAwareElementVisitor(languageLevel) {
override fun visitPyElement(node: PyElement) {
super.visitPyElement(node)
checkAddElement(node)
}
override fun visitPyClass(node: PyClass) {
checkAddElement(node) // do not recurse into classes
}
override fun visitPyFunction(node: PyFunction) {
checkAddElement(node) // do not recurse into functions
}
protected abstract fun checkAddElement(node: PsiElement?)
}
@@ -0,0 +1,81 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.impl
import com.intellij.openapi.util.Version
import com.intellij.psi.util.QualifiedName
import com.jetbrains.python.PyTokenTypes
import com.jetbrains.python.ast.*
import com.jetbrains.python.ast.impl.PyPsiUtilsCore
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
import java.math.BigInteger
@ApiStatus.Internal
data class PyVersionCheck(val version: Version, val isLessThan: Boolean) {
fun matches(languageLevel: LanguageLevel): Boolean {
return isLessThan == version.compareTo(languageLevel.majorVersion, languageLevel.minorVersion) > 0
}
companion object {
/**
* Extracts the Python version comparison from {@code ifPart}'s condition if it's a version check as specified in
* <a href="https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks">Version and Platform Checks</a> E.g.
* <pre>{@code
* if sys.version_info >= (3,):
* ...
* }</pre>
* @return A {@link VersionCheck} instance if {@code ifPart} is a (valid) version check, or {@code null} otherwise.
*/
@JvmStatic
fun fromCondition(ifPart: PyAstIfPart): PyVersionCheck? {
val binaryExpr = PyPsiUtilsCore.flattenParens(ifPart.condition)
if (binaryExpr !is PyAstBinaryExpression) return null
val lhsRefExpr = PyPsiUtilsCore.flattenParens(binaryExpr.leftExpression)
if (lhsRefExpr !is PyAstReferenceExpression) return null
if (SYS_VERSION_INFO_QUALIFIED_NAME != lhsRefExpr.asQualifiedName()) return null
val versionTuple = PyPsiUtilsCore.flattenParens(binaryExpr.rightExpression)
if (versionTuple !is PyAstTupleExpression<*>) return null
val version = evaluateVersion(versionTuple)
if (version == null) return null
val operator = binaryExpr.getOperator()
if (operator !== PyTokenTypes.LT && operator !== PyTokenTypes.GE) return null
return PyVersionCheck(version, operator === PyTokenTypes.LT)
}
private val SYS_VERSION_INFO_QUALIFIED_NAME = QualifiedName.fromDottedString("sys.version_info")
private fun evaluateVersion(versionTuple: PyAstTupleExpression<*>): Version? {
val elements = versionTuple.elements
if (elements.size != 1 && elements.size != 2) {
return null
}
val major = evaluateNumber(elements[0])
if (major == null) {
return null
}
if (elements.size == 1) {
return Version(major, 0, 0)
}
val minor = evaluateNumber(elements[1])
if (minor == null) {
return null
}
return Version(major, minor, 0)
}
private fun evaluateNumber(expression: PyAstExpression?): Int? {
if (expression !is PyAstNumericLiteralExpression) return null
if (!expression.isIntegerLiteral) return null
val value = expression.bigIntegerValue ?: return null
val intValue = value.toInt()
return if (BigInteger.valueOf(intValue.toLong()) == value) intValue else null
}
}
}
@@ -0,0 +1,8 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.stubs;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.psi.PyElsePart;
public interface PyElsePartStub extends StubElement<PyElsePart> {
}
@@ -0,0 +1,10 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.stubs;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.psi.impl.PyVersionCheck;
import com.jetbrains.python.psi.PyIfPartElif;
public interface PyIfPartElifStub extends StubElement<PyIfPartElif> {
PyVersionCheck getVersionCheck();
}
@@ -0,0 +1,10 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.stubs;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.psi.impl.PyVersionCheck;
import com.jetbrains.python.psi.PyIfPartIf;
public interface PyIfPartIfStub extends StubElement<PyIfPartIf> {
PyVersionCheck getVersionCheck();
}
@@ -4,6 +4,7 @@ package com.jetbrains.python
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.IStubElementType
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.IReparseableElementType
import com.jetbrains.python.psi.impl.*
import java.util.function.Function
@@ -51,6 +52,12 @@ class PyElementTypesFacadeImpl : PyElementTypesFacade() {
get() = PyStubElementTypes.TYPE_PARAMETER_LIST
override val typeAliasStatement: IStubElementType<*, *>
get() = PyStubElementTypes.TYPE_ALIAS_STATEMENT
override val ifPartIf: IElementType
get() = PyStubElementTypes.IF_PART_IF
override val ifPartElif: IElementType
get() = PyStubElementTypes.IF_PART_ELIF
override val elsePart: IElementType
get() = PyStubElementTypes.ELSE_PART
override val statementList: IReparseableElementType
get() = PyStatementListElementType()
@@ -170,10 +177,6 @@ class PyElementTypesFacadeImpl : PyElementTypesFacade() {
get() = F { node -> PySetCompExpressionImpl(node) }
override val dictCompExpressionConstructor: F
get() = F { node -> PyDictCompExpressionImpl(node) }
override val ifPartIfConstructor: F
get() = F { node -> PyIfPartIfImpl(node) }
override val ifPartElifConstructor: F
get() = F { node -> PyIfPartElifImpl(node) }
override val forPartConstructor: F
get() = F { node -> PyForPartImpl(node) }
override val whilePartConstructor: F
@@ -182,8 +185,6 @@ class PyElementTypesFacadeImpl : PyElementTypesFacade() {
get() = F { node -> PyTryPartImpl(node) }
override val finallyPartConstructor: F
get() = F { node -> PyFinallyPartImpl(node) }
override val elsePartConstructor: F
get() = F { node -> PyElsePartImpl(node) }
override val fStringNodeConstructor: F
get() = F { node -> PyFormattedStringElementImpl(node) }
override val fStringFragmentConstructor: F
@@ -33,4 +33,8 @@ public interface PyStubElementTypes {
PyStubElementType<PyTypeParameterStub, PyTypeParameter> TYPE_PARAMETER = new PyTypeParameterElementType();
PyStubElementType<PyTypeParameterListStub, PyTypeParameterList> TYPE_PARAMETER_LIST = new PyTypeParameterListElementType();
PyStubElementType<PyTypeAliasStatementStub, PyTypeAliasStatement> TYPE_ALIAS_STATEMENT = new PyTypeAliasStatementElementType();
PyStubElementType<PyIfPartIfStub, PyIfPartIf> IF_PART_IF = new PyIfPartIfElementType();
PyStubElementType<PyIfPartElifStub, PyIfPartElif> IF_PART_ELIF = new PyIfPartElifElementType();
PyStubElementType<PyElsePartStub, PyElsePart> ELSE_PART = new PyElsePartElementType();
}
@@ -18,6 +18,8 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeVariable;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.impl.PyVersionAwareElementVisitor;
import com.jetbrains.python.pyi.PyiUtil;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -214,7 +216,14 @@ public class ScopeImpl implements Scope {
final Set<String> nonlocals = new HashSet<>();
final Set<String> augAssignments = new HashSet<>();
final List<PyTargetExpression> targetExpressions = new ArrayList<>();
myFlowOwner.acceptChildren(new PyRecursiveElementVisitor() {
final LanguageLevel languageLevel;
if (myFlowOwner instanceof PyFile || myFlowOwner instanceof PyClass) {
languageLevel = PyiUtil.getOriginalLanguageLevel(myFlowOwner);
}
else {
languageLevel = null;
}
myFlowOwner.acceptChildren(new PyVersionAwareElementVisitor(languageLevel) {
@Override
public void visitPyTargetExpression(@NotNull PyTargetExpression node) {
targetExpressions.add(node);
@@ -60,7 +60,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 89;
return 90;
}
@Nullable
@@ -642,8 +642,9 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
final Property decoratedProperty = processDecoratedProperties(filter);
if (decoratedProperty != null) return decoratedProperty;
if (getStub() != null) {
return processStubProperties(filter);
PyClassStub stub = getStub();
if (stub != null) {
return processStubProperties(stub, filter);
}
else {
// name = property(...) assignments from PSI
@@ -721,10 +722,12 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
}
@Nullable
private Property processStubProperties(@Nullable Processor<? super Property> filter) {
final PyClassStub stub = getStub();
if (stub != null) {
for (StubElement<?> subStub : stub.getChildrenStubs()) {
private Property processStubProperties(@NotNull PyClassStub stub, @Nullable Processor<? super Property> filter) {
class StubPropertiesProcessor implements Processor<StubElement<?>> {
private @Nullable Property myResult;
@Override
public boolean process(StubElement<?> subStub) {
if (subStub.getStubType() == PyElementTypes.TARGET_EXPRESSION) {
final PyTargetExpressionStub targetStub = (PyTargetExpressionStub)subStub;
final PropertyStubStorage prop = targetStub.getCustomStub(PropertyStubStorage.class);
@@ -735,13 +738,19 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
final String doc = prop.getDoc();
if (getter != NONE || setter != NONE || deleter != NONE) {
final PropertyImpl property = new PropertyImpl(targetStub.getName(), getter, setter, deleter, doc, targetStub.getPsi());
if (filter == null || filter.process(property)) return property;
if (filter == null || filter.process(property)) {
myResult = property;
return false;
}
}
}
}
return true;
}
}
return null;
StubPropertiesProcessor processor = new StubPropertiesProcessor();
PyPsiUtils.processChildrenStubs(stub, PyiUtil.getOriginalLanguageLevel(this), processor);
return processor.myResult;
}
@Nullable
@@ -1014,28 +1023,39 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
@Override
public List<PyTargetExpression> getClassAttributes() {
LanguageLevel languageLevel = PyiUtil.getOriginalLanguageLevel(this);
final ArrayList<PyTargetExpression> result = new ArrayList<>();
PyClassStub stub = getStub();
if (stub != null) {
final PyTargetExpression[] children = stub.getChildrenByType(PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.EMPTY_ARRAY);
return Arrays.asList(children);
PyPsiUtils.processChildrenStubs(stub, languageLevel, child -> {
if (child.getStubType() == PyElementTypes.TARGET_EXPRESSION) {
result.add((PyTargetExpression)child.getPsi());
}
return true;
});
}
List<PyTargetExpression> result = new ArrayList<>();
for (PsiElement psiElement : getStatementList().getChildren()) {
if (psiElement instanceof PyAssignmentStatement assignmentStatement) {
final PyExpression[] targets = assignmentStatement.getTargets();
for (PyExpression target : targets) {
if (target instanceof PyTargetExpression) {
result.add((PyTargetExpression)target);
else {
getStatementList().acceptChildren(new PyVersionAwareTopLevelElementVisitor(languageLevel) {
@Override
protected void checkAddElement(PsiElement psiElement) {
if (psiElement instanceof PyAssignmentStatement assignmentStatement) {
final PyExpression[] targets = assignmentStatement.getTargets();
for (PyExpression target : targets) {
if (target instanceof PyTargetExpression) {
result.add((PyTargetExpression)target);
}
}
}
else if (psiElement instanceof PyTypeDeclarationStatement) {
final PyExpression target = ((PyTypeDeclarationStatement)psiElement).getTarget();
if (target instanceof PyTargetExpression) {
result.add((PyTargetExpression)target);
}
}
}
}
else if (psiElement instanceof PyTypeDeclarationStatement) {
final PyExpression target = ((PyTypeDeclarationStatement)psiElement).getTarget();
if (target instanceof PyTargetExpression) {
result.add((PyTargetExpression)target);
}
}
});
}
result.trimToSize();
return result;
}
@@ -1233,11 +1253,9 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
public boolean processClassLevelDeclarations(@NotNull PsiScopeProcessor processor) {
final PyClassStub stub = getStub();
if (stub != null) {
final List<StubElement<?>> children = stub.getChildrenStubs();
for (StubElement<?> child : children) {
if (!processor.execute(child.getPsi(), ResolveState.initial())) {
return false;
}
LanguageLevel languageLevel = PyiUtil.getOriginalLanguageLevel(this);
if (!PyPsiUtils.processChildrenStubs(stub, languageLevel, child -> processor.execute(child.getPsi(), ResolveState.initial()))) {
return false;
}
}
else {
@@ -2,14 +2,22 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.psi.PyConditionalStatementPart;
import com.jetbrains.python.psi.PyElementVisitor;
public abstract class PyConditionalStatementPartImpl extends PyElementImpl implements PyConditionalStatementPart {
public abstract class PyConditionalStatementPartImpl<T extends StubElement<? extends PsiElement>> extends PyBaseElementImpl<T>
implements PyConditionalStatementPart {
public PyConditionalStatementPartImpl(ASTNode astNode) {
super(astNode);
}
protected PyConditionalStatementPartImpl(T stub, IStubElementType<?, ?> elementType) {
super(stub, elementType);
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyConditionalStatementPart(this);
@@ -16,11 +16,16 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.PyStubElementTypes;
import com.jetbrains.python.psi.PyElsePart;
import com.jetbrains.python.psi.stubs.PyElsePartStub;
public class PyElsePartImpl extends PyElementImpl implements PyElsePart {
public class PyElsePartImpl extends PyBaseElementImpl<PyElsePartStub> implements PyElsePart {
public PyElsePartImpl(ASTNode astNode) {
super(astNode);
}
public PyElsePartImpl(PyElsePartStub stub) {
super(stub, PyStubElementTypes.ELSE_PART);
}
}
@@ -70,7 +70,8 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
myModificationStamp = modificationStamp;
final StubElement<?> stub = getStub();
processDeclarations(PyPsiUtils.collectAllStubChildren(PyFileImpl.this, stub), element -> {
LanguageLevel languageLevel = PyiUtil.getOriginalLanguageLevel(PyFileImpl.this);
processDeclarations(PyFileImpl.this, stub, languageLevel, element -> {
if (element instanceof PsiNamedElement namedElement &&
!(element instanceof PyKeywordArgument) &&
!(stub == null && element.getParent() instanceof PyImportElement)) {
@@ -101,13 +102,16 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
Collections.reverse(myImportedNameDefiners);
}
private static boolean processDeclarations(@NotNull List<PsiElement> elements, @NotNull Processor<? super PsiElement> processor) {
for (PsiElement child : elements) {
private static boolean processDeclarations(@NotNull PsiElement element,
@Nullable StubElement<?> stub,
@NotNull LanguageLevel languageLevel,
@NotNull Processor<? super PsiElement> processor) {
for (PsiElement child : collectAllChildren(element, stub, languageLevel)) {
if (!processor.process(child)) {
return false;
}
if (child instanceof PyExceptPart part) {
if (!processDeclarations(PyPsiUtils.collectAllStubChildren(part, part.getStub()), processor)) {
if (!processDeclarations(part, part.getStub(), languageLevel, processor)) {
return false;
}
}
@@ -228,7 +232,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
@Override
public @NotNull List<PyTypeAliasStatement> getTypeAliasStatements() {
return PyPsiUtils.collectStubChildren(this, getGreenStub(), PyTypeAliasStatement.class);
return collectChildren(PyTypeAliasStatement.class);
}
@Override
@@ -370,18 +374,18 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
@Override
@NotNull
public List<PyClass> getTopLevelClasses() {
return PyPsiUtils.collectStubChildren(this, getGreenStub(), PyClass.class);
return collectChildren(PyClass.class);
}
@NotNull
@Override
public List<PyFunction> getTopLevelFunctions() {
return PyPsiUtils.collectStubChildren(this, getGreenStub(), PyFunction.class);
return collectChildren(PyFunction.class);
}
@Override
public List<PyTargetExpression> getTopLevelAttributes() {
return PyPsiUtils.collectStubChildren(this, getGreenStub(), PyTargetExpression.class);
return collectChildren(PyTargetExpression.class);
}
@Override
@@ -490,7 +494,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
@NotNull
public List<PyImportElement> getImportTargets() {
final List<PyImportElement> ret = new ArrayList<>();
final List<PyImportStatement> imports = PyPsiUtils.collectStubChildren(this, getGreenStub(), PyImportStatement.class);
final List<PyImportStatement> imports = collectChildren(PyImportStatement.class);
for (PyImportStatement one : imports) {
ContainerUtil.addAll(ret, one.getImportElements());
}
@@ -500,7 +504,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
@Override
@NotNull
public List<PyFromImportStatement> getFromImports() {
return PyPsiUtils.collectStubChildren(this, getGreenStub(), PyFromImportStatement.class);
return collectChildren(PyFromImportStatement.class);
}
@Nullable
@@ -845,4 +849,68 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
}
};
}
@NotNull
private <T extends PyElement> List<T> collectChildren(Class<T> type) {
return collectChildren(this, getGreenStub(), PyiUtil.getOriginalLanguageLevel(this), type);
}
@NotNull
private static <T extends PyElement> List<T> collectChildren(@NotNull PsiElement element,
@Nullable StubElement<?> stub,
@NotNull LanguageLevel languageLevel,
@NotNull Class<T> type) {
final List<T> result = new ArrayList<>();
if (stub != null) {
PyPsiUtils.processChildrenStubs(stub, languageLevel, child -> {
PsiElement childPsi = child.getPsi();
if (type.isInstance(childPsi)) {
result.add(type.cast(childPsi));
}
return true;
});
}
else {
element.acceptChildren(new PyVersionAwareTopLevelElementVisitor(languageLevel) {
@Override
protected void checkAddElement(PsiElement node) {
if (type.isInstance(node)) {
result.add(type.cast(node));
}
}
@Override
public void visitPyStatement(@NotNull PyStatement node) {
if (PyStatement.class.isAssignableFrom(type) && !(node instanceof PyCompoundStatement)) {
checkAddElement(node);
return;
}
super.visitPyStatement(node);
}
});
}
return result;
}
@NotNull
private static List<PsiElement> collectAllChildren(@NotNull PsiElement element,
@Nullable StubElement<?> stub,
@NotNull LanguageLevel languageLevel) {
List<PsiElement> result = new ArrayList<>();
if (stub != null) {
PyPsiUtils.processChildrenStubs(stub, languageLevel, child -> {
result.add(child.getPsi());
return true;
});
}
else {
element.acceptChildren(new PyVersionAwareTopLevelElementVisitor(languageLevel) {
@Override
protected void checkAddElement(PsiElement node) {
result.add(node);
}
});
}
return result;
}
}
@@ -29,10 +29,7 @@ import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.icons.PythonPsiApiIcons;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.stubs.PyAnnotationOwnerStub;
import com.jetbrains.python.psi.stubs.PyClassStub;
import com.jetbrains.python.psi.stubs.PyFunctionStub;
import com.jetbrains.python.psi.stubs.PyTargetExpressionStub;
import com.jetbrains.python.psi.stubs.*;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.sdk.PythonSdkUtil;
import org.jetbrains.annotations.NotNull;
@@ -133,9 +130,9 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
public @Nullable PyClass getContainingClass() {
final PyFunctionStub stub = getStub();
if (stub != null) {
final StubElement parentStub = stub.getParentStub();
if (parentStub instanceof PyClassStub) {
return ((PyClassStub)parentStub).getPsi();
final StubElement<?> parentStub = PyPsiUtils.getParentStubSkippingVersionChecks(stub);
if (parentStub instanceof PyClassStub classStub) {
return classStub.getPsi();
}
return null;
@@ -2,13 +2,19 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.PyStubElementTypes;
import com.jetbrains.python.psi.PyIfPartElif;
import com.jetbrains.python.psi.stubs.PyIfPartElifStub;
/**
* PyIfPart that represents an 'elif' part.
*/
public class PyIfPartElifImpl extends PyConditionalStatementPartImpl implements PyIfPartElif {
public class PyIfPartElifImpl extends PyConditionalStatementPartImpl<PyIfPartElifStub> implements PyIfPartElif {
public PyIfPartElifImpl(ASTNode astNode) {
super(astNode);
}
public PyIfPartElifImpl(PyIfPartElifStub stub) {
super(stub, PyStubElementTypes.IF_PART_ELIF);
}
}
@@ -2,15 +2,19 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.PyStubElementTypes;
import com.jetbrains.python.psi.PyIfPartIf;
import com.jetbrains.python.psi.stubs.PyIfPartIfStub;
/**
* PyIfPart that represents an 'if' part.
*/
public class PyIfPartIfImpl extends PyConditionalStatementPartImpl implements PyIfPartIf {
public class PyIfPartIfImpl extends PyConditionalStatementPartImpl<PyIfPartIfStub> implements PyIfPartIf {
public PyIfPartIfImpl(ASTNode astNode) {
super(astNode);
}
public PyIfPartIfImpl(PyIfPartIfStub stub) {
super(stub, PyStubElementTypes.IF_PART_IF);
}
}
@@ -590,7 +590,7 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl<PyTargetExpression
public PyClass getContainingClass() {
final PyTargetExpressionStub stub = getStub();
if (stub != null) {
final StubElement parentStub = stub.getParentStub();
final StubElement parentStub = PyPsiUtils.getParentStubSkippingVersionChecks(stub);
if (parentStub instanceof PyClassStub) {
return ((PyClassStub)parentStub).getPsi();
}
@@ -16,9 +16,10 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.stubs.StubElement;
import com.jetbrains.python.psi.PyWhilePart;
public class PyWhilePartImpl extends PyConditionalStatementPartImpl implements PyWhilePart {
public class PyWhilePartImpl extends PyConditionalStatementPartImpl<StubElement<PyWhilePart>> implements PyWhilePart {
public PyWhilePartImpl(ASTNode astNode) {
super(astNode);
}
@@ -161,7 +161,7 @@ public class PyClassElementType extends PyStubElementType<PyClassStub, PyClass>
final List<String> slots = PyFileElementType.readNullableList(dataStream);
final String docStringInStub = dataStream.readUTFFast();
final String docString = docStringInStub.length() > 0 ? docStringInStub : null;
final String docString = !docStringInStub.isEmpty() ? docStringInStub : null;
final String deprecationMessage = dataStream.readNameString();
@@ -177,7 +177,7 @@ public class PyClassElementType extends PyStubElementType<PyClassStub, PyClass>
if (name != null) {
sink.occurrence(PyClassNameIndex.KEY, name);
sink.occurrence(PyClassNameIndexInsensitive.KEY, StringUtil.toLowerCase(name));
if (stub.getParentStub() instanceof PyFileStub && PyUtil.getInitialUnderscores(name) == 0) {
if (PyPsiUtils.getParentStubSkippingVersionChecks(stub) instanceof PyFileStub && PyUtil.getInitialUnderscores(name) == 0) {
sink.occurrence(PyExportedModuleAttributeIndex.KEY, name);
}
}
@@ -0,0 +1,41 @@
package com.jetbrains.python.psi.impl.stubs
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.StubElement
import com.intellij.psi.stubs.StubInputStream
import com.intellij.psi.stubs.StubOutputStream
import com.jetbrains.python.psi.impl.PyVersionCheck
import com.jetbrains.python.psi.PyElsePart
import com.jetbrains.python.psi.PyIfStatement
import com.jetbrains.python.psi.PyStubElementType
import com.jetbrains.python.psi.impl.PyElsePartImpl
import com.jetbrains.python.psi.stubs.PyElsePartStub
class PyElsePartElementType : PyStubElementType<PyElsePartStub, PyElsePart>("ELSE_PART") {
override fun createPsi(stub: PyElsePartStub): PyElsePart {
return PyElsePartImpl(stub)
}
override fun createStub(psi: PyElsePart, parentStub: StubElement<out PsiElement>): PyElsePartStub {
return PyElsePartStubImpl(parentStub)
}
override fun serialize(stub: PyElsePartStub, dataStream: StubOutputStream) {
}
override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): PyElsePartStub {
return PyElsePartStubImpl(parentStub)
}
override fun createElement(node: ASTNode): PsiElement {
return PyElsePartImpl(node)
}
override fun shouldCreateStub(node: ASTNode): Boolean {
val ifStatement = node.treeParent?.psi as? PyIfStatement ?: return false
if (!isFileOrClassTopLevel(ifStatement)) return false
val ifParts = sequenceOf(ifStatement.ifPart) + ifStatement.elifParts.asSequence()
return ifParts.all { PyVersionCheck.fromCondition(it) != null }
}
}
@@ -0,0 +1,9 @@
package com.jetbrains.python.psi.impl.stubs
import com.intellij.psi.stubs.StubBase
import com.intellij.psi.stubs.StubElement
import com.jetbrains.python.PyStubElementTypes
import com.jetbrains.python.psi.PyElsePart
import com.jetbrains.python.psi.stubs.PyElsePartStub
class PyElsePartStubImpl(parent: StubElement<*>) : StubBase<PyElsePart>(parent, PyStubElementTypes.ELSE_PART), PyElsePartStub
@@ -19,7 +19,6 @@ import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.*;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyStubElementTypes;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyStringLiteralExpression;
@@ -116,7 +115,7 @@ public class PyFunctionElementType extends PyStubElementType<PyFunctionStub, PyF
final String name = stub.getName();
if (name != null) {
sink.occurrence(PyFunctionNameIndex.KEY, name);
if (stub.getParentStub() instanceof PyFileStub && PyUtil.getInitialUnderscores(name) == 0) {
if (PyPsiUtils.getParentStubSkippingVersionChecks(stub) instanceof PyFileStub && PyUtil.getInitialUnderscores(name) == 0) {
sink.occurrence(PyExportedModuleAttributeIndex.KEY, name);
}
}
@@ -0,0 +1,44 @@
package com.jetbrains.python.psi.impl.stubs
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.StubElement
import com.intellij.psi.stubs.StubInputStream
import com.intellij.psi.stubs.StubOutputStream
import com.intellij.util.takeWhileInclusive
import com.jetbrains.python.psi.impl.PyVersionCheck
import com.jetbrains.python.psi.PyIfPartElif
import com.jetbrains.python.psi.PyIfStatement
import com.jetbrains.python.psi.PyStubElementType
import com.jetbrains.python.psi.impl.PyIfPartElifImpl
import com.jetbrains.python.psi.stubs.PyIfPartElifStub
class PyIfPartElifElementType : PyStubElementType<PyIfPartElifStub, PyIfPartElif>("IF_PART_ELIF") {
override fun createPsi(stub: PyIfPartElifStub): PyIfPartElif {
return PyIfPartElifImpl(stub)
}
override fun createStub(psi: PyIfPartElif, parentStub: StubElement<out PsiElement>): PyIfPartElifStub {
return PyIfPartElifStubImpl(parentStub, requireNotNull(PyVersionCheck.fromCondition (psi)))
}
override fun serialize(stub: PyIfPartElifStub, dataStream: StubOutputStream) {
serializeVersionCheck(stub.versionCheck, dataStream)
}
override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): PyIfPartElifStub {
return PyIfPartElifStubImpl(parentStub, deserializeVersionCheck(dataStream))
}
override fun createElement(node: ASTNode): PsiElement {
return PyIfPartElifImpl(node)
}
override fun shouldCreateStub(node: ASTNode): Boolean {
val ifStatement = node.treeParent?.psi as? PyIfStatement ?: return false
if (!isFileOrClassTopLevel(ifStatement)) return false
return (sequenceOf(ifStatement.ifPart) + ifStatement.elifParts.asSequence())
.takeWhileInclusive { it !== node.psi }
.all { PyVersionCheck.fromCondition(it) != null }
}
}
@@ -0,0 +1,13 @@
package com.jetbrains.python.psi.impl.stubs
import com.intellij.psi.stubs.StubBase
import com.intellij.psi.stubs.StubElement
import com.jetbrains.python.PyStubElementTypes
import com.jetbrains.python.psi.impl.PyVersionCheck
import com.jetbrains.python.psi.PyIfPartElif
import com.jetbrains.python.psi.stubs.PyIfPartElifStub
class PyIfPartElifStubImpl(parent: StubElement<*>, private val versionCheck: PyVersionCheck)
: StubBase<PyIfPartElif>(parent, PyStubElementTypes.IF_PART_ELIF), PyIfPartElifStub {
override fun getVersionCheck(): PyVersionCheck = versionCheck
}
@@ -0,0 +1,38 @@
package com.jetbrains.python.psi.impl.stubs
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.StubElement
import com.intellij.psi.stubs.StubInputStream
import com.intellij.psi.stubs.StubOutputStream
import com.jetbrains.python.psi.impl.PyVersionCheck
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyIfPartIfImpl
import com.jetbrains.python.psi.stubs.PyIfPartIfStub
class PyIfPartIfElementType : PyStubElementType<PyIfPartIfStub, PyIfPartIf>("IF_PART_IF") {
override fun createPsi(stub: PyIfPartIfStub): PyIfPartIf {
return PyIfPartIfImpl(stub)
}
override fun createStub(psi: PyIfPartIf, parentStub: StubElement<out PsiElement>): PyIfPartIfStub {
return PyIfPartIfStubImpl(parentStub, requireNotNull(PyVersionCheck.fromCondition (psi)))
}
override fun serialize(stub: PyIfPartIfStub, dataStream: StubOutputStream) {
serializeVersionCheck(stub.versionCheck, dataStream)
}
override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): PyIfPartIfStub {
return PyIfPartIfStubImpl(parentStub, deserializeVersionCheck(dataStream))
}
override fun createElement(node: ASTNode): PsiElement {
return PyIfPartIfImpl(node)
}
override fun shouldCreateStub(node: ASTNode): Boolean {
val psi = node.psi as PyIfPartIf
return isFileOrClassTopLevel(psi) && PyVersionCheck.fromCondition(psi) != null
}
}
@@ -0,0 +1,13 @@
package com.jetbrains.python.psi.impl.stubs
import com.intellij.psi.stubs.StubBase
import com.intellij.psi.stubs.StubElement
import com.jetbrains.python.PyStubElementTypes
import com.jetbrains.python.psi.impl.PyVersionCheck
import com.jetbrains.python.psi.PyIfPartIf
import com.jetbrains.python.psi.stubs.PyIfPartIfStub
class PyIfPartIfStubImpl(parent: StubElement<*>, private val versionCheck: PyVersionCheck)
: StubBase<PyIfPartIf>(parent, PyStubElementTypes.IF_PART_IF), PyIfPartIfStub {
override fun getVersionCheck(): PyVersionCheck = versionCheck
}
@@ -0,0 +1,29 @@
package com.jetbrains.python.psi.impl.stubs
import com.intellij.openapi.util.Version
import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.StubInputStream
import com.intellij.psi.stubs.StubOutputStream
import com.intellij.psi.util.PsiTreeUtil
import com.jetbrains.python.psi.impl.PyVersionCheck
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.PyFunction
internal fun isFileOrClassTopLevel(element: PsiElement): Boolean {
val parent = PsiTreeUtil.getParentOfType(element, PyFile::class.java, PyClass::class.java, PyFunction::class.java)
return parent is PyFile || parent is PyClass
}
internal fun serializeVersionCheck(versionCheck: PyVersionCheck, dataStream: StubOutputStream) {
dataStream.writeBoolean(versionCheck.isLessThan)
dataStream.writeVarInt(versionCheck.version.major)
dataStream.writeVarInt(versionCheck.version.minor)
}
internal fun deserializeVersionCheck(dataStream: StubInputStream): PyVersionCheck {
val isLessThan = dataStream.readBoolean()
val major = dataStream.readVarInt()
val minor = dataStream.readVarInt()
return PyVersionCheck(Version(major, minor, 0), isLessThan)
}
@@ -15,6 +15,7 @@ import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.impl.PyTargetExpressionImpl;
import com.jetbrains.python.psi.stubs.PyExportedModuleAttributeIndex;
import com.jetbrains.python.psi.stubs.PyFileStub;
@@ -149,7 +150,7 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
public void indexStub(@NotNull PyTargetExpressionStub stub, @NotNull IndexSink sink) {
String name = stub.getName();
if (name != null && PyUtil.getInitialUnderscores(name) == 0) {
if (stub.getParentStub() instanceof PyFileStub) {
if (PyPsiUtils.getParentStubSkippingVersionChecks(stub) instanceof PyFileStub) {
sink.occurrence(PyVariableNameIndex.KEY, name);
sink.occurrence(PyExportedModuleAttributeIndex.KEY, name);
}
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.pyi;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
@@ -105,14 +106,20 @@ public final class PyiUtil {
@NotNull
public static LanguageLevel getOriginalLanguageLevel(@NotNull PyElement element) {
PsiFile containingFile = element.getContainingFile();
if (containingFile instanceof PyiFile) {
if (containingFile instanceof PyiFile pyiFile) {
PsiElement impl = getOriginalElement(element);
if (impl != null) {
return LanguageLevel.forElement(impl);
}
else {
// XXX: Relying on the fact .pyi files still have the language level key set by the pusher
return PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(element.getProject(), containingFile.getVirtualFile());
VirtualFile vFile = containingFile.getVirtualFile();
if (vFile != null) {
return PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(element.getProject(), vFile);
}
else {
return pyiFile.getLanguageLevel();
}
}
}
return LanguageLevel.forElement(element);
@@ -12,10 +12,7 @@ import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.stubs.StubElement;
import com.intellij.util.xmlb.annotations.OptionTag;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.ast.PyAstClass;
import com.jetbrains.python.ast.PyAstFile;
import com.jetbrains.python.ast.PyAstFunction;
import com.jetbrains.python.ast.PyAstTargetExpression;
import com.jetbrains.python.ast.*;
import com.jetbrains.python.ast.impl.PyPsiUtilsCore;
import com.jetbrains.python.defaultProjectAwareService.PyDefaultProjectAwareService;
import com.jetbrains.python.defaultProjectAwareService.PyDefaultProjectAwareServiceClasses;
@@ -101,9 +98,14 @@ public abstract class PyDocumentationSettings
@Nullable
private static PyAstTargetExpression getDocFormatAttribute(@NotNull List<StubElement<?>> stubs) {
for (StubElement<?> stub : stubs) {
if (stub.getPsi() instanceof PyAstTargetExpression targetExpression && PyNames.DOCFORMAT.equals(targetExpression.getName())) {
PsiElement psi = stub.getPsi();
if (psi instanceof PyAstTargetExpression targetExpression && PyNames.DOCFORMAT.equals(targetExpression.getName())) {
return targetExpression;
}
if (psi instanceof PyAstIfPart || psi instanceof PyAstElsePart) {
PyAstTargetExpression targetExpression = getDocFormatAttribute(stub.getChildrenStubs());
if (targetExpression != null) return targetExpression;
}
}
return null;
}
@@ -0,0 +1,30 @@
import sys
if condition1:
class MyClass:
if condition2:
attr0 = 0
def f0():
pass
class MyClass0:
pass
if sys.version_info >= (3,):
attr1 = 1
def f1():
pass
class MyClass1:
pass
elif sys.version_info < (2, 6):
attr2 = 2
def f2():
pass
class MyClass2:
pass
else:
attr3 = 3
def f3():
pass
class MyClass3:
pass
MyClass.<caret>
@@ -0,0 +1,28 @@
import sys
if condition:
attr0 = 0
def f0():
pass
class MyClass0:
pass
if sys.version_info >= (3,):
attr1 = 1
def f1():
pass
class MyClass1:
pass
elif sys.version_info < (2, 6):
attr2 = 2
def f2():
pass
class MyClass2:
pass
else:
attr3 = 3
def f3():
pass
class MyClass3:
pass
<caret>
@@ -0,0 +1,21 @@
import sys
if condition1:
class MyClass:
if condition2:
if sys.version_info < (4,):
def foo(self):
class MyNestedClass:
def f0(self):
pass
if sys.version_info >= (3,):
def f1(self):
pass
elif sys.version_info < (2, 6):
def f2(self):
pass
else:
def f3(self):
pass
MyNestedClass().<caret>
@@ -4,4 +4,4 @@ TypeVar("T", int, str, bound=int, covariant=True, contravariant=True)
TypeVar("T", int, str, bound='int', covariant=True, contravariant=True)
TypeVar("T", int, 'str', bound=int, covariant=True, contravariant=True)
TypeVar("T", 'int', 'str', bound=int, covariant=True, contravariant=True)
TypeVar<warning descr="Unexpected type(s):(str, int, int, int, int, int)Possible type(s):(str, Any | None, bool, bool, Any, Any)(str, Any | None, bool, bool, Any, Any)" textAttributesKey="WARNING_ATTRIBUTES">("T", 0, 1, bound=2, covariant=3, contravariant=4)</warning>
TypeVar("T", 0, 1, bound=2, <warning descr="Expected type 'bool', got 'int' instead" textAttributesKey="WARNING_ATTRIBUTES">covariant=3</warning>, <warning descr="Expected type 'bool', got 'int' instead" textAttributesKey="WARNING_ATTRIBUTES">contravariant=4</warning>)
@@ -1 +1 @@
<html><body><div class="bottom"><icon src="AllIcons.Nodes.Class"/>&nbsp;<code><a href="psi_element://#typename#TypingExtensionsSelf.A">TypingExtensionsSelf.A</a></code></div><div class="definition"><pre><span style="color:#000080;font-weight:bold;">def </span><span style="color:#000000;">foo</span><span style="">(</span><span style="color:#94558d;">self</span><span style="">)</span> -&gt; <span style="color:#000000;">Self</span></pre></div></body></html>
<html><body><div class="bottom"><icon src="AllIcons.Nodes.Class"/>&nbsp;<code><a href="psi_element://#typename#TypingExtensionsSelf.A">TypingExtensionsSelf.A</a></code></div><div class="definition"><pre><span style="color:#000080;font-weight:bold;">def </span><span style="color:#000000;">foo</span><span style="">(</span><span style="color:#94558d;">self</span><span style="">)</span> -&gt; <span style="color:#000000;"><a href="psi_element://#typename#typing._SpecialForm">Self</a></span></pre></div></body></html>
@@ -0,0 +1,13 @@
import sys
if sys.version_info < (4,):
class MyClass:
if sys.version_info >= (3,):
def foo(self):
pass
elif sys.version_info <= (2, 5):
def bar(self):
pass
else:
def buz(self):
pass
@@ -0,0 +1,8 @@
import sys
if True:
if sys.version_info >= (3,):
if sys.version_info < (3, 12):
foo = 23
else:
bar = -1
@@ -0,0 +1,21 @@
import sys
if condition1:
if sys.version_info >= (3,):
if sys.version_info < (3, 12):
def foo(): ...
else:
if sys.version_info < (2, 2):
pass
elif sys.version_info < (2, 5):
pass
else:
class MyClass:
if sys.version_info < (3, 12):
class MyNestedClass:
if sys.version_info >= (3, 11):
pass
else:
pass
else:
pass
@@ -0,0 +1,15 @@
import sys
if sys.version_info < (2, ):
pass
else:
class Foo:
if sys.version_info < (3, 2):
def foo(self) -> int:
pass
elif sys.version_info < (3, 5):
def foo(self) -> float:
pass
else:
def foo(self) -> str:
pass
@@ -0,0 +1,8 @@
import sys
if sys.version_info < (3, 8):
def foo() -> int:
pass
else:
def foo() -> str:
pass
@@ -414,14 +414,19 @@ public class Py3CompletionTest extends PyTestCase {
// PY-27148
public void testNamedTupleSpecial() {
final List<String> suggested = doTestByText("""
from collections import namedtuple
class Cat1(namedtuple("Cat", "name age")):
pass
c1 = Cat1("name", 5)
c1.<caret>""");
assertNotNull(suggested);
assertContainsElements(suggested, PyNamedTupleType.NAMEDTUPLE_SPECIAL_ATTRIBUTES);
runWithLanguageLevel(
LanguageLevel.PYTHON37,
() -> {
final List<String> suggested = doTestByText("""
from collections import namedtuple
class Cat1(namedtuple("Cat", "name age")):
pass
c1 = Cat1("name", 5)
c1.<caret>""");
assertNotNull(suggested);
assertContainsElements(suggested, PyNamedTupleType.NAMEDTUPLE_SPECIAL_ATTRIBUTES);
}
);
}
// PY-33254, PY-12339, PY-40834
@@ -2381,6 +2381,28 @@ public class Py3TypeTest extends PyTestCase {
""");
}
// PY-34617
public void testTopLevelFunctionUnderVersionCheck() {
runWithLanguageLevel(LanguageLevel.PYTHON310, () -> {
doMultiFileTest("str",
"""
from mod import foo
expr = foo()
""");
});
}
// PY-34617
public void testClassMethodUnderVersionCheck() {
runWithLanguageLevel(LanguageLevel.PYTHON34, () -> {
doMultiFileTest("float",
"""
from mod import Foo
expr = Foo().foo()
""");
});
}
private void doTest(final String expectedType, final String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final PyExpression expr = myFixture.findElementByText("expr", PyExpression.class);
@@ -2,6 +2,7 @@
package com.jetbrains.python;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
@@ -31,6 +32,31 @@ public class PyOverloadsResolutionTest extends PyTestCase {
""");
}
// PY-34617
public void testFunctionOverloadNotMatchingVersionCheckIsIgnoredInPyFile() {
runWithLanguageLevel(LanguageLevel.PYTHON34, () -> {
doTest("object",
"""
from typing import overload
import sys
if sys.version_info < (3,):
@overload
def func(x: str) -> str:
pass
else:
@overload
def func(x: object) -> object:
pass
def func(x):
...
expr = func("foo")
""");
});
}
public void testFirstMatchingFunctionOverloadSelectedInSiblingPyiFile() {
doMultiFileStubAwareTest("str");
}
@@ -60,7 +86,7 @@ public class PyOverloadsResolutionTest extends PyTestCase {
@overload
def method(self, x: object) -> object:
pass
def method(self, x):
pass
@@ -68,6 +94,31 @@ public class PyOverloadsResolutionTest extends PyTestCase {
""");
}
// PY-34617
public void testMethodOverloadNotMatchingVersionCheckIsIgnoredInPyFile() {
runWithLanguageLevel(LanguageLevel.PYTHON310, () -> {
doTest("object",
"""
from typing import overload
class C:
if sys.version_info < (3,):
@overload
def method(self, x: str) -> str:
pass
else:
@overload
def method(self, x: object) -> object:
pass
def method(self, x):
pass
expr = C().method("foo")
""");
});
}
public void testFirstMatchingMethodOverloadSelectedInSiblingPyiFile() {
doMultiFileStubAwareTest("str");
}
@@ -978,9 +978,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
feignCtrlP(offset).check(Arrays.asList("self: TypeVar, name: str, *constraints, bound: Any | None = None, covariant: bool = False, " +
"contravariant: bool = False, infer_variance: bool = False",
"self: TypeVar, name: str, *constraints, bound: Any | None = None, covariant: bool = False, " +
"contravariant: bool = False"),
"contravariant: bool = False, infer_variance: bool = False"),
Arrays.asList(new String[]{"name: str, "}, new String[]{"name: str, "}),
Arrays.asList(new String[]{"self: TypeVar, "}, new String[]{"self: TypeVar, "}));
}
@@ -5,6 +5,7 @@ import com.intellij.lang.FileASTNode;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Version;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiFileImpl;
@@ -18,6 +19,7 @@ import com.intellij.testFramework.TestDataPath;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.ast.PyAstFunction;
import com.jetbrains.python.psi.impl.PyVersionCheck;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.*;
@@ -130,6 +132,43 @@ public class PyStubsTest extends PyTestCase {
);
}
// PY-34617
public void testStubStructureWithVersionChecks() {
final PsiFileImpl file = (PsiFileImpl)getTestFile();
element(PyFileStub.class)
.withChildren(
element(PyImportStatementStub.class),
ifPart(3, 0, false)
.withChildren(
ifPart(3, 12, true)
.withChildren(
element(PyFunctionStub.class)
)
),
element(PyElsePartStub.class)
.withChildren(
ifPart(2, 2, true),
elifPart(2, 5, true),
element(PyElsePartStub.class)
.withChildren(
element(PyClassStub.class)
.withChildren(
ifPart(3, 12, true)
.withChildren(
element(PyClassStub.class)
.withChildren(
ifPart(3, 11, false),
element(PyElsePartStub.class)
)
),
element(PyElsePartStub.class)
)
)
)
)
.test(file.getStub());
}
public void testLoadingDeeperTreeRemainsKnownPsiElement() {
final PyFile file = getTestFile();
final List<PyClass> classes = file.getTopLevelClasses();
@@ -282,13 +321,14 @@ public class PyStubsTest extends PyTestCase {
}
public void testImportInExcept() {
final PyFileImpl file = (PyFileImpl) getTestFile();
final PsiElement element = file.getElementNamed("tzinfo");
assertTrue(element != null ? element.toString() : "null", element instanceof PyClass);
assertNotParsed(file);
runWithLanguageLevel(LanguageLevel.PYTHON26, () -> {
final PyFileImpl file = (PyFileImpl)getTestFile();
final PsiElement element = file.getElementNamed("tzinfo");
assertTrue(String.valueOf(element), element instanceof PyClass);
assertNotParsed(file);
});
}
public void testImportFeatures() {
final PyFileImpl file = (PyFileImpl) getTestFile();
assertTrue(file.hasImportFromFuture(FutureFeature.DIVISION));
@@ -1123,7 +1163,6 @@ public class PyStubsTest extends PyTestCase {
doTestTypeParameterStub(typeAliasStatement, file);
}
private void doTestTypingTypedDictArguments() {
doTestTypedDict("name", Arrays.asList("x", "y"), Arrays.asList("str", "int"), QualifiedName.fromComponents("TypedDict"));
}
@@ -1233,4 +1272,37 @@ public class PyStubsTest extends PyTestCase {
assertEquals(initValue, fieldStub.initValue());
}
}
private static @NotNull StubElementValidator element(@NotNull Class<?> clazz) {
return stub -> assertInstanceOf(stub, clazz);
}
private static @NotNull StubElementValidator ifPart(int major, int minor, boolean isLessThan) {
return stub -> {
assertInstanceOf(stub, PyIfPartIfStub.class);
assertEquals(new PyVersionCheck(new Version(major, minor, 0), isLessThan), ((PyIfPartIfStub)stub).getVersionCheck());
};
}
private static @NotNull StubElementValidator elifPart(int major, int minor, boolean isLessThan) {
return stub -> {
assertInstanceOf(stub, PyIfPartElifStub.class);
assertEquals(new PyVersionCheck(new Version(major, minor, 0), isLessThan), ((PyIfPartElifStub)stub).getVersionCheck());
};
}
private interface StubElementValidator {
void test(@NotNull StubElement<?> stub);
default @NotNull StubElementValidator withChildren(StubElementValidator @NotNull ... children) {
return stub -> {
test(stub);
List<StubElement<?>> childrenStubs = stub.getChildrenStubs();
assertSize(children.length, childrenStubs);
for (int i = 0; i != children.length; i++) {
children[i].test(childrenStubs.get(i));
}
};
}
}
}