mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
PY-34617 Take into account sys.version_info checks when analyzing Python files
Support and, or, <=, > operators in version checks. GitOrigin-RevId: 5006e88b0f7935d0bf0841dfd5fad5c371e8ff12
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0b8f4cc1e8
commit
79dc479c63
@@ -16,9 +16,12 @@ import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.types.PyClassTypeImpl;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.pyi.PyiUtil;
|
||||
import junit.framework.TestCase;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class PyCommonResolveTest extends PyCommonResolveTestCase {
|
||||
|
||||
@Override
|
||||
@@ -1920,71 +1923,182 @@ public abstract class PyCommonResolveTest extends PyCommonResolveTestCase {
|
||||
}
|
||||
|
||||
// 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();
|
||||
public void testModuleAttributeUnderVersionCheck() {
|
||||
String decl = """
|
||||
import sys
|
||||
|
||||
if True:
|
||||
if sys.version_info >= (3,):
|
||||
if sys.version_info >= (3, 10) and sys.version_info < (3, 12):
|
||||
foo = 23
|
||||
if sys.version_info < (3, 11) and (sys.version_info < (3, 5) or sys.version_info > (3, 7)):
|
||||
buz = 23
|
||||
else:
|
||||
bar = -1
|
||||
|
||||
""";
|
||||
|
||||
String foo = decl + """
|
||||
foo
|
||||
<ref>""";
|
||||
Consumer<PsiElement> fooTargetExpr = e -> assertResolveResult(e, PyTargetExpression.class, "foo", null);
|
||||
String buz = decl + """
|
||||
buz
|
||||
<ref>""";
|
||||
Consumer<PsiElement> buzTargetExpr = e -> assertResolveResult(e, PyTargetExpression.class, "buz", null);
|
||||
String bar = decl + """
|
||||
bar
|
||||
<ref>""";
|
||||
Consumer<PsiElement> barTargetExpr = e -> assertResolveResult(e, PyTargetExpression.class, "bar", null);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, foo, fooTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, buz, buzTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON312, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON312, buz, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON312, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON38, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON38, buz, buzTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON38, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON37, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON37, buz, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON37, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON34, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON34, buz, buzTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON34, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, buz, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, bar, barTargetExpr);
|
||||
}
|
||||
|
||||
// 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();
|
||||
public void testModuleAttributeUnderVersionCheckMultifile() {
|
||||
myFixture.copyDirectoryToProject("resolve/ModuleAttributeUnderVersionCheck", "");
|
||||
String foo = """
|
||||
import mod
|
||||
mod.foo
|
||||
<ref>""";
|
||||
Consumer<PsiElement> fooTargetExpr = e -> assertResolveResult(e, PyTargetExpression.class, "foo", "mod.py");
|
||||
String buz = """
|
||||
import mod
|
||||
mod.buz
|
||||
<ref>""";
|
||||
Consumer<PsiElement> buzTargetExpr = e -> assertResolveResult(e, PyTargetExpression.class, "buz", "mod.py");
|
||||
String bar = """
|
||||
import mod
|
||||
mod.bar
|
||||
<ref>""";
|
||||
Consumer<PsiElement> barTargetExpr = e -> assertResolveResult(e, PyTargetExpression.class, "bar", "mod.py");
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, foo, fooTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, buz, buzTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON312, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON312, buz, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON312, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON38, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON38, buz, buzTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON38, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON37, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON37, buz, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON37, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON34, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON34, buz, buzTargetExpr);
|
||||
assertResolvedElement(LanguageLevel.PYTHON34, bar, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, buz, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, bar, barTargetExpr);
|
||||
}
|
||||
|
||||
// PY-34617
|
||||
public void testClassAttributeMatchingVersionCheck() {
|
||||
public void testClassAttributeUnderVersionCheck() {
|
||||
String classDecl = """
|
||||
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
|
||||
|
||||
""";
|
||||
|
||||
String foo = classDecl + """
|
||||
MyClass().foo()
|
||||
<ref>""";
|
||||
String bar = classDecl + """
|
||||
MyClass().bar()
|
||||
<ref>""";
|
||||
String buz = classDecl + """
|
||||
MyClass().buz()
|
||||
<ref>""";
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, foo, e -> assertResolveResult(e, PyFunction.class, "foo", null));
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, bar, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, buz, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON24, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON24, bar, e -> assertResolveResult(e, PyFunction.class, "bar", null));
|
||||
assertResolvedElement(LanguageLevel.PYTHON24, buz, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, bar, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, buz, e -> assertResolveResult(e, PyFunction.class, "buz", null));
|
||||
}
|
||||
|
||||
// PY-34617
|
||||
public void testClassAttributeUnderVersionCheckMultifile() {
|
||||
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();
|
||||
String foo = """
|
||||
from mod import MyClass
|
||||
m = MyClass()
|
||||
m.foo()
|
||||
<ref>""";
|
||||
String bar = """
|
||||
from mod import MyClass
|
||||
m = MyClass()
|
||||
m.bar()
|
||||
<ref>""";
|
||||
String buz = """
|
||||
from mod import MyClass
|
||||
m = MyClass()
|
||||
m.buz()
|
||||
<ref>""";
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, foo, e -> assertResolveResult(e, PyFunction.class, "foo", "mod.py"));
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, bar, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON310, buz, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON24, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON24, bar, e -> assertResolveResult(e, PyFunction.class, "bar", "mod.py"));
|
||||
assertResolvedElement(LanguageLevel.PYTHON24, buz, TestCase::assertNull);
|
||||
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, foo, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, bar, TestCase::assertNull);
|
||||
assertResolvedElement(LanguageLevel.PYTHON27, buz, e -> assertResolveResult(e, PyFunction.class, "buz", "mod.py"));
|
||||
}
|
||||
|
||||
// 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);
|
||||
private void assertResolvedElement(@NotNull LanguageLevel languageLevel, @NotNull String text, @NotNull Consumer<PsiElement> assertion) {
|
||||
runWithLanguageLevel(languageLevel, () -> {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
PsiElement element = PyCommonResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve();
|
||||
assertion.accept(element);
|
||||
});
|
||||
assertFilesNotParsed();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.psi.PyIfStatement
|
||||
import com.jetbrains.python.psi.PyRecursiveElementVisitor
|
||||
@@ -10,20 +11,22 @@ 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() {
|
||||
open class PyVersionAwareElementVisitor(languageLevel: LanguageLevel?) : PyRecursiveElementVisitor() {
|
||||
private val version = languageLevel?.let { Version(it.majorVersion, it.minorVersion, 0) }
|
||||
|
||||
override fun visitPyIfStatement(node: PyIfStatement) {
|
||||
if (languageLevel == null) {
|
||||
if (version == null) {
|
||||
super.visitPyIfStatement(node)
|
||||
return
|
||||
}
|
||||
val ifParts = sequenceOf(node.getIfPart()) + node.elifParts.asSequence()
|
||||
val ifParts = sequenceOf(node.ifPart) + node.elifParts.asSequence()
|
||||
for (ifPart in ifParts) {
|
||||
val versionCheck = PyVersionCheck.fromCondition(ifPart)
|
||||
if (versionCheck == null) {
|
||||
val versions = ifPart.condition?.let(PyVersionCheck::convertToVersionRanges)
|
||||
if (versions == null) {
|
||||
super.visitPyIfStatement(node)
|
||||
return
|
||||
}
|
||||
if (versionCheck.matches(languageLevel)) {
|
||||
if (versions.contains(version)) {
|
||||
ifPart.statementList.accept(this)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,85 +1,88 @@
|
||||
// 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.google.common.collect.ImmutableRangeSet
|
||||
import com.google.common.collect.Range
|
||||
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 == languageLevel.isLessThan(version)
|
||||
}
|
||||
|
||||
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
|
||||
object PyVersionCheck {
|
||||
/**
|
||||
* @return Version ranges if {@code expression} is a version check, {@code null} otherwise
|
||||
*
|
||||
* @see <a href="https://peps.python.org/pep-0484/#version-and-platform-checking">Version and Platform Checks</a>
|
||||
*/
|
||||
@JvmStatic
|
||||
fun convertToVersionRanges(expression: PyAstExpression): ImmutableRangeSet<Version>? {
|
||||
val binaryExpr = PyPsiUtilsCore.flattenParens(expression) as? PyAstBinaryExpression ?: return null
|
||||
when (val operator = binaryExpr.operator) {
|
||||
PyTokenTypes.AND_KEYWORD, PyTokenTypes.OR_KEYWORD -> {
|
||||
val rhs = binaryExpr.rightExpression ?: return null
|
||||
val ranges1 = convertToVersionRanges(binaryExpr.leftExpression) ?: return null
|
||||
val ranges2 = convertToVersionRanges(rhs) ?: return null
|
||||
return if (operator === PyTokenTypes.AND_KEYWORD)
|
||||
ranges1.intersection(ranges2)
|
||||
else
|
||||
ranges1.union(ranges2)
|
||||
}
|
||||
|
||||
val major = evaluateNumber(elements[0])
|
||||
if (major == null) {
|
||||
return null
|
||||
PyTokenTypes.LT, PyTokenTypes.GT, PyTokenTypes.LE, PyTokenTypes.GE -> {
|
||||
val refExpr = PyPsiUtilsCore.flattenParens(binaryExpr.leftExpression) as? PyAstReferenceExpression ?: return null
|
||||
if (SYS_VERSION_INFO_QUALIFIED_NAME != refExpr.asQualifiedName()) return null
|
||||
|
||||
val tuple = PyPsiUtilsCore.flattenParens(binaryExpr.rightExpression) as? PyAstTupleExpression<*> ?: return null
|
||||
val version = evaluateVersion(tuple) ?: return null
|
||||
|
||||
val range = when (operator) {
|
||||
PyTokenTypes.LT -> Range.lessThan(version)
|
||||
PyTokenTypes.GT -> Range.greaterThan(version)
|
||||
PyTokenTypes.LE -> Range.atMost(version)
|
||||
PyTokenTypes.GE -> Range.atLeast(version)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
return ImmutableRangeSet.of(range)
|
||||
}
|
||||
|
||||
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
|
||||
val intValue = value.toInt()
|
||||
return if (BigInteger.valueOf(intValue.toLong()) == value) intValue else null
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LanguageLevel.isLessThan(version: Version): Boolean {
|
||||
return version.compareTo(majorVersion, minorVersion) > 0
|
||||
}
|
||||
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
|
||||
val intValue = value.toInt()
|
||||
return if (BigInteger.valueOf(intValue.toLong()) == value) intValue else null
|
||||
}
|
||||
}
|
||||
@@ -1,10 +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.stubs
|
||||
|
||||
import com.google.common.collect.RangeSet
|
||||
import com.intellij.openapi.util.Version
|
||||
|
||||
interface PyVersionSpecificStub {
|
||||
val versionRange: PyVersionRange
|
||||
val versions: RangeSet<Version>
|
||||
}
|
||||
|
||||
data class PyVersionRange(val lowInclusive: Version?, val highExclusive: Version?)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.stubs.*;
|
||||
@@ -55,7 +57,7 @@ public class PyClassElementType extends PyStubElementType<PyClassStub, PyClass>
|
||||
PyPsiUtils.strValue(psi.getDocStringExpression()),
|
||||
psi.getDeprecationMessage(),
|
||||
getStubElementType(),
|
||||
PyVersionSpecificStubBaseKt.evaluateVersionRangeForElement(psi),
|
||||
PyVersionSpecificStubBaseKt.evaluateVersionsForElement(psi),
|
||||
createCustomStub(psi));
|
||||
}
|
||||
|
||||
@@ -136,7 +138,7 @@ public class PyClassElementType extends PyStubElementType<PyClassStub, PyClass>
|
||||
dataStream.writeUTFFast(docString != null ? docString : "");
|
||||
dataStream.writeName(pyClassStub.getDeprecationMessage());
|
||||
|
||||
PyVersionSpecificStubBaseKt.serializeVersionRange(pyClassStub.getVersionRange(), dataStream);
|
||||
PyVersionSpecificStubBaseKt.serializeVersions(pyClassStub.getVersions(), dataStream);
|
||||
|
||||
serializeCustomStub(pyClassStub.getCustomStub(PyCustomClassStub.class), dataStream);
|
||||
}
|
||||
@@ -168,12 +170,12 @@ public class PyClassElementType extends PyStubElementType<PyClassStub, PyClass>
|
||||
|
||||
final String deprecationMessage = dataStream.readNameString();
|
||||
|
||||
final PyVersionRange versionRange = PyVersionSpecificStubBaseKt.deserializeVersionRange(dataStream);
|
||||
final RangeSet<Version> versions = PyVersionSpecificStubBaseKt.deserializeVersions(dataStream);
|
||||
|
||||
final PyCustomClassStub customStub = deserializeCustomStub(dataStream);
|
||||
|
||||
return new PyClassStubImpl(name, parentStub, superClasses, baseClassesText, metaClass, slots, docString, deprecationMessage,
|
||||
getStubElementType(), versionRange, customStub);
|
||||
getStubElementType(), versions, customStub);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.psi.stubs.IStubElementType;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.stubs.PyClassStub;
|
||||
import com.jetbrains.python.psi.stubs.PyVersionRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -49,9 +50,9 @@ public class PyClassStubImpl extends PyVersionSpecificStubBase<PyClass> implemen
|
||||
@Nullable String docString,
|
||||
@Nullable String deprecationMessage,
|
||||
@NotNull IStubElementType stubElementType,
|
||||
@NotNull PyVersionRange versionRange,
|
||||
@NotNull RangeSet<Version> versions,
|
||||
@Nullable PyCustomClassStub customStub) {
|
||||
super(parentStub, stubElementType, versionRange);
|
||||
super(parentStub, stubElementType, versions);
|
||||
myName = name;
|
||||
mySuperClasses = superClasses;
|
||||
mySuperClassesText = superClassesText;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.stubs.*;
|
||||
@@ -60,7 +62,7 @@ public class PyFunctionElementType extends PyStubElementType<PyFunctionStub, PyF
|
||||
final PyStringLiteralExpression docStringExpression = function.getDocStringExpression();
|
||||
final String typeComment = function.getTypeCommentAnnotation();
|
||||
final String annotationContent = function.getAnnotationValue();
|
||||
final PyVersionRange versionRange = PyVersionSpecificStubBaseKt.evaluateVersionRangeForElement(psi);
|
||||
final RangeSet<Version> versions = PyVersionSpecificStubBaseKt.evaluateVersionsForElement(psi);
|
||||
return new PyFunctionStubImpl(psi.getName(),
|
||||
PyPsiUtils.strValue(docStringExpression),
|
||||
message,
|
||||
@@ -71,7 +73,7 @@ public class PyFunctionElementType extends PyStubElementType<PyFunctionStub, PyF
|
||||
annotationContent,
|
||||
parentStub,
|
||||
getStubElementType(),
|
||||
versionRange);
|
||||
versions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,7 +86,7 @@ public class PyFunctionElementType extends PyStubElementType<PyFunctionStub, PyF
|
||||
dataStream.writeBoolean(stub.onlyRaisesNotImplementedError());
|
||||
dataStream.writeName(stub.getTypeComment());
|
||||
dataStream.writeName(stub.getAnnotation());
|
||||
PyVersionSpecificStubBaseKt.serializeVersionRange(stub.getVersionRange(), dataStream);
|
||||
PyVersionSpecificStubBaseKt.serializeVersions(stub.getVersions(), dataStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,7 +100,7 @@ public class PyFunctionElementType extends PyStubElementType<PyFunctionStub, PyF
|
||||
final boolean onlyRaisesNotImplementedError = dataStream.readBoolean();
|
||||
String typeComment = dataStream.readNameString();
|
||||
String annotationContent = dataStream.readNameString();
|
||||
PyVersionRange versionRange = PyVersionSpecificStubBaseKt.deserializeVersionRange(dataStream);
|
||||
RangeSet<Version> versions = PyVersionSpecificStubBaseKt.deserializeVersions(dataStream);
|
||||
return new PyFunctionStubImpl(name,
|
||||
StringUtil.nullize(docString),
|
||||
deprecationMessage,
|
||||
@@ -109,7 +111,7 @@ public class PyFunctionElementType extends PyStubElementType<PyFunctionStub, PyF
|
||||
annotationContent,
|
||||
parentStub,
|
||||
getStubElementType(),
|
||||
versionRange);
|
||||
versions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.psi.stubs.IStubElementType;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionStub;
|
||||
import com.jetbrains.python.psi.stubs.PyVersionRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -29,8 +30,8 @@ public class PyFunctionStubImpl extends PyVersionSpecificStubBase<PyFunction> im
|
||||
@Nullable String annotation,
|
||||
final StubElement parent,
|
||||
@NotNull IStubElementType stubElementType,
|
||||
@NotNull PyVersionRange versionRange) {
|
||||
super(parent, stubElementType, versionRange);
|
||||
@NotNull RangeSet<Version> versions) {
|
||||
super(parent, stubElementType, versions);
|
||||
myName = name;
|
||||
myDocString = docString;
|
||||
myDeprecationMessage = deprecationMessage;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.source.tree.TreeUtil;
|
||||
import com.intellij.psi.stubs.IndexSink;
|
||||
@@ -55,12 +57,12 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
|
||||
final String docString = DocStringUtil.getDocStringValue(psi);
|
||||
final String typeComment = psi.getTypeCommentAnnotation();
|
||||
final String annotation = psi.getAnnotationValue();
|
||||
final PyVersionRange versionRange = PyVersionSpecificStubBaseKt.evaluateVersionRangeForElement(psi);
|
||||
final RangeSet<Version> versions = PyVersionSpecificStubBaseKt.evaluateVersionsForElement(psi);
|
||||
|
||||
CustomTargetExpressionStub customStub = createCustomStub(psi);
|
||||
if (customStub != null) {
|
||||
return new PyTargetExpressionStubImpl(name, docString, typeComment, annotation, psi.hasAssignedValue(), customStub, parentStub,
|
||||
versionRange);
|
||||
versions);
|
||||
}
|
||||
|
||||
PyTargetExpressionStub.InitializerType initializerType = PyTargetExpressionStub.InitializerType.Other;
|
||||
@@ -78,7 +80,7 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
|
||||
}
|
||||
}
|
||||
return new PyTargetExpressionStubImpl(name, docString, initializerType, initializer, psi.isQualified(), typeComment, annotation,
|
||||
psi.hasAssignedValue(), parentStub, versionRange);
|
||||
psi.hasAssignedValue(), parentStub, versions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,7 +92,7 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
|
||||
stream.writeName(stub.getTypeComment());
|
||||
stream.writeName(stub.getAnnotation());
|
||||
stream.writeBoolean(stub.hasAssignedValue());
|
||||
PyVersionSpecificStubBaseKt.serializeVersionRange(stub.getVersionRange(), stream);
|
||||
PyVersionSpecificStubBaseKt.serializeVersions(stub.getVersions(), stream);
|
||||
final CustomTargetExpressionStub customStub = stub.getCustomStub(CustomTargetExpressionStub.class);
|
||||
if (customStub != null) {
|
||||
serializeCustomStub(customStub, stream);
|
||||
@@ -113,15 +115,15 @@ public class PyTargetExpressionElementType extends PyStubElementType<PyTargetExp
|
||||
String typeComment = stream.readNameString();
|
||||
String annotation = stream.readNameString();
|
||||
final boolean hasAssignedValue = stream.readBoolean();
|
||||
PyVersionRange versionRange = PyVersionSpecificStubBaseKt.deserializeVersionRange(stream);
|
||||
RangeSet<Version> versions = PyVersionSpecificStubBaseKt.deserializeVersions(stream);
|
||||
if (initializerType == PyTargetExpressionStub.InitializerType.Custom) {
|
||||
CustomTargetExpressionStub stub = deserializeCustomStub(stream);
|
||||
return new PyTargetExpressionStubImpl(name, docString, typeComment, annotation, hasAssignedValue, stub, parentStub, versionRange);
|
||||
return new PyTargetExpressionStubImpl(name, docString, typeComment, annotation, hasAssignedValue, stub, parentStub, versions);
|
||||
}
|
||||
QualifiedName initializer = QualifiedName.deserialize(stream);
|
||||
boolean isQualified = stream.readBoolean();
|
||||
return new PyTargetExpressionStubImpl(name, docString, initializerType, initializer, isQualified, typeComment, annotation,
|
||||
hasAssignedValue, parentStub, versionRange);
|
||||
hasAssignedValue, parentStub, versions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
@@ -22,7 +24,6 @@ import com.intellij.util.ObjectUtils;
|
||||
import com.jetbrains.python.PyStubElementTypes;
|
||||
import com.jetbrains.python.psi.PyTargetExpression;
|
||||
import com.jetbrains.python.psi.stubs.PyTargetExpressionStub;
|
||||
import com.jetbrains.python.psi.stubs.PyVersionRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -46,8 +47,8 @@ public class PyTargetExpressionStubImpl extends PyVersionSpecificStubBase<PyTarg
|
||||
boolean hasAssignedValue,
|
||||
CustomTargetExpressionStub customStub,
|
||||
StubElement parent,
|
||||
@NotNull PyVersionRange versionRange) {
|
||||
super(parent, PyStubElementTypes.TARGET_EXPRESSION, versionRange);
|
||||
@NotNull RangeSet<Version> versions) {
|
||||
super(parent, PyStubElementTypes.TARGET_EXPRESSION, versions);
|
||||
myName = name;
|
||||
myTypeComment = typeComment;
|
||||
myAnnotation = annotation;
|
||||
@@ -68,8 +69,8 @@ public class PyTargetExpressionStubImpl extends PyVersionSpecificStubBase<PyTarg
|
||||
@Nullable String annotation,
|
||||
boolean hasAssignedValue,
|
||||
final StubElement parentStub,
|
||||
@NotNull PyVersionRange versionRange) {
|
||||
super(parentStub, PyStubElementTypes.TARGET_EXPRESSION, versionRange);
|
||||
@NotNull RangeSet<Version> versions) {
|
||||
super(parentStub, PyStubElementTypes.TARGET_EXPRESSION, versions);
|
||||
myName = name;
|
||||
myTypeComment = typeComment;
|
||||
myAnnotation = annotation;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.stubs.IStubElementType;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
@@ -11,7 +13,6 @@ import com.jetbrains.python.psi.PyStubElementType;
|
||||
import com.jetbrains.python.psi.PyTypeAliasStatement;
|
||||
import com.jetbrains.python.psi.impl.PyTypeAliasStatementImpl;
|
||||
import com.jetbrains.python.psi.stubs.PyTypeAliasStatementStub;
|
||||
import com.jetbrains.python.psi.stubs.PyVersionRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -37,14 +38,14 @@ public class PyTypeAliasStatementElementType extends PyStubElementType<PyTypeAli
|
||||
@NotNull
|
||||
public PyTypeAliasStatementStub createStub(@NotNull PyTypeAliasStatement psi, StubElement<? extends PsiElement> parentStub) {
|
||||
return new PyTypeAliasStatementStubImpl(psi.getName(), (psi.getTypeExpression() != null ? psi.getTypeExpression().getText() : null),
|
||||
parentStub, getStubElementType(), PyVersionSpecificStubBaseKt.evaluateVersionRangeForElement(psi));
|
||||
parentStub, getStubElementType(), PyVersionSpecificStubBaseKt.evaluateVersionsForElement(psi));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(@NotNull PyTypeAliasStatementStub stub, @NotNull StubOutputStream dataStream) throws IOException {
|
||||
dataStream.writeName(stub.getName());
|
||||
dataStream.writeName(stub.getTypeExpressionText());
|
||||
PyVersionSpecificStubBaseKt.serializeVersionRange(stub.getVersionRange(), dataStream);
|
||||
PyVersionSpecificStubBaseKt.serializeVersions(stub.getVersions(), dataStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,9 +53,9 @@ public class PyTypeAliasStatementElementType extends PyStubElementType<PyTypeAli
|
||||
public PyTypeAliasStatementStub deserialize(@NotNull StubInputStream dataStream, StubElement parentStub) throws IOException {
|
||||
String name = dataStream.readNameString();
|
||||
String typeExpressionText = dataStream.readNameString();
|
||||
PyVersionRange versionRange = PyVersionSpecificStubBaseKt.deserializeVersionRange(dataStream);
|
||||
RangeSet<Version> versions = PyVersionSpecificStubBaseKt.deserializeVersions(dataStream);
|
||||
|
||||
return new PyTypeAliasStatementStubImpl(name, typeExpressionText, parentStub, getStubElementType(), versionRange);
|
||||
return new PyTypeAliasStatementStubImpl(name, typeExpressionText, parentStub, getStubElementType(), versions);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.jetbrains.python.psi.impl.stubs;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.openapi.util.Version;
|
||||
import com.intellij.psi.stubs.IStubElementType;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.jetbrains.python.psi.PyTypeAliasStatement;
|
||||
import com.jetbrains.python.psi.stubs.PyTypeAliasStatementStub;
|
||||
import com.jetbrains.python.psi.stubs.PyVersionRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -17,8 +18,8 @@ public class PyTypeAliasStatementStubImpl extends PyVersionSpecificStubBase<PyTy
|
||||
@Nullable String typeExpressionText,
|
||||
@Nullable StubElement parent,
|
||||
@NotNull IStubElementType stubElementType,
|
||||
@NotNull PyVersionRange versionRange) {
|
||||
super(parent, stubElementType, versionRange);
|
||||
@NotNull RangeSet<Version> versions) {
|
||||
super(parent, stubElementType, versions);
|
||||
myName = name;
|
||||
myTypeExpressionText = typeExpressionText;
|
||||
}
|
||||
|
||||
@@ -1,116 +1,141 @@
|
||||
package com.jetbrains.python.psi.impl.stubs
|
||||
|
||||
import com.google.common.collect.BoundType
|
||||
import com.google.common.collect.ImmutableRangeSet
|
||||
import com.google.common.collect.Range
|
||||
import com.google.common.collect.RangeSet
|
||||
import com.google.common.collect.TreeRangeSet
|
||||
import com.intellij.openapi.util.Version
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.stubs.*
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.psi.PyElsePart
|
||||
import com.jetbrains.python.psi.PyIfPart
|
||||
import com.jetbrains.python.psi.PyIfStatement
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.PyVersionCheck
|
||||
import com.jetbrains.python.psi.impl.isLessThan
|
||||
import com.jetbrains.python.psi.stubs.PyVersionRange
|
||||
import com.jetbrains.python.psi.stubs.PyVersionSpecificStub
|
||||
|
||||
internal abstract class PyVersionSpecificStubBase<T : PsiElement>(
|
||||
parent: StubElement<*>?,
|
||||
elementType: IStubElementType<*, *>?,
|
||||
override val versionRange: PyVersionRange,
|
||||
override val versions: RangeSet<Version>,
|
||||
) : StubBase<T>(parent, elementType), PyVersionSpecificStub
|
||||
|
||||
internal fun getChildrenStubs(stub: StubElement<*>, languageLevel: LanguageLevel): Iterable<StubElement<*>> {
|
||||
val version = Version(languageLevel.majorVersion, languageLevel.minorVersion, 0)
|
||||
return stub.childrenStubs.asSequence()
|
||||
.filter { it !is PyVersionSpecificStub || it.versionRange.contains(languageLevel) }
|
||||
.filter { it !is PyVersionSpecificStub || it.versions.contains(version) }
|
||||
.asIterable()
|
||||
}
|
||||
|
||||
private fun PyVersionRange.contains(languageLevel: LanguageLevel): Boolean {
|
||||
val low = lowInclusive
|
||||
val high = highExclusive
|
||||
return (low == null || !languageLevel.isLessThan(low)) &&
|
||||
(high == null || languageLevel.isLessThan(high))
|
||||
}
|
||||
|
||||
internal fun evaluateVersionRangeForElement(element: PsiElement): PyVersionRange {
|
||||
internal fun evaluateVersionsForElement(element: PsiElement): ImmutableRangeSet<Version> {
|
||||
return CachedValuesManager.getCachedValue(element) {
|
||||
val parent = element.parent
|
||||
var range: PyVersionRange
|
||||
var result: ImmutableRangeSet<Version>
|
||||
if (parent == null) {
|
||||
range = PyVersionRange(null, null)
|
||||
result = ImmutableRangeSet.of(Range.all())
|
||||
}
|
||||
else {
|
||||
range = evaluateVersionRangeForElement(parent)
|
||||
result = evaluateVersionsForElement(parent)
|
||||
if (parent is PyIfPart || parent is PyElsePart) {
|
||||
val grandParent = parent.parent
|
||||
if (grandParent is PyIfStatement) {
|
||||
range = evaluateRange(range, grandParent, parent)
|
||||
if (grandParent is PyIfStatement && element === (parent as PyStatementPart).statementList) {
|
||||
val versions = evaluateVersionRangeForIfStatementPart(grandParent, parent)
|
||||
if (versions != null) {
|
||||
result = result.intersection(versions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CachedValueProvider.Result.create(range, element)
|
||||
CachedValueProvider.Result.create(result, element)
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateRange(
|
||||
initialRange: PyVersionRange,
|
||||
ifStatement: PyIfStatement,
|
||||
ifStatementPart: PsiElement,
|
||||
): PyVersionRange {
|
||||
val versionChecks = mutableListOf<PyVersionCheck>()
|
||||
private fun evaluateVersionRangeForIfStatementPart(ifStatement: PyIfStatement, ifStatementPart: PsiElement): RangeSet<Version>? {
|
||||
assert(ifStatementPart is PyIfPart || ifStatementPart is PyElsePart)
|
||||
val result = if (ifStatementPart is PyIfPart) {
|
||||
val versionRanges = ifStatementPart.condition?.let(PyVersionCheck::convertToVersionRanges) ?: return null
|
||||
TreeRangeSet.create(versionRanges)
|
||||
}
|
||||
else {
|
||||
TreeRangeSet.create(listOf(Range.all<Version>()))
|
||||
}
|
||||
val ifParts = sequenceOf(ifStatement.ifPart) + ifStatement.elifParts.asSequence()
|
||||
for (ifPart in ifParts.takeWhile { it !== ifStatementPart }) {
|
||||
val versionCheck = PyVersionCheck.fromCondition(ifPart) ?: return initialRange
|
||||
versionChecks.add(PyVersionCheck(versionCheck.version, !versionCheck.isLessThan))
|
||||
val versionRanges = ifPart.condition?.let(PyVersionCheck::convertToVersionRanges) ?: return null
|
||||
result.removeAll(versionRanges)
|
||||
}
|
||||
if (ifStatementPart is PyIfPart) {
|
||||
val versionCheck = PyVersionCheck.fromCondition(ifStatementPart) ?: return initialRange
|
||||
versionChecks.add(versionCheck)
|
||||
}
|
||||
return versionChecks.fold(initialRange, ::clampRange)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun clampRange(versionRange: PyVersionRange, versionCheck: PyVersionCheck): PyVersionRange {
|
||||
return if (versionCheck.isLessThan)
|
||||
PyVersionRange(versionRange.lowInclusive, min(versionRange.highExclusive, versionCheck.version))
|
||||
internal fun serializeVersions(versions: RangeSet<Version>, outputStream: StubOutputStream) {
|
||||
val ranges = versions.asRanges()
|
||||
outputStream.writeVarInt(ranges.size)
|
||||
for (range in ranges) {
|
||||
serializeRange(range, outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
private fun serializeRange(range: Range<Version>, outputStream: StubOutputStream) {
|
||||
val low = if (range.hasLowerBound()) Endpoint(range.lowerEndpoint(), range.lowerBoundType()) else null
|
||||
val high = if (range.hasUpperBound()) Endpoint(range.upperEndpoint(), range.upperBoundType()) else null
|
||||
serializeEndpoint(low, outputStream)
|
||||
serializeEndpoint(high, outputStream)
|
||||
}
|
||||
|
||||
private fun serializeEndpoint(endpoint: Endpoint?, outputStream: StubOutputStream) {
|
||||
if (endpoint == null) {
|
||||
outputStream.writeByte(EndpointType.UNBOUND)
|
||||
}
|
||||
else {
|
||||
val endpointType = when (endpoint.boundType) {
|
||||
BoundType.OPEN -> EndpointType.OPEN
|
||||
BoundType.CLOSED -> EndpointType.CLOSED
|
||||
}
|
||||
outputStream.writeByte(endpointType)
|
||||
outputStream.writeVarInt(endpoint.version.major)
|
||||
outputStream.writeVarInt(endpoint.version.minor)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun deserializeVersions(stream: StubInputStream): RangeSet<Version> {
|
||||
val size = stream.readVarInt()
|
||||
val builder = ImmutableRangeSet.builder<Version>()
|
||||
repeat(size) {
|
||||
builder.add(deserializeRange(stream))
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun deserializeRange(stream: StubInputStream): Range<Version> {
|
||||
val low = deserializeEndpoint(stream)
|
||||
val high = deserializeEndpoint(stream)
|
||||
return if (low != null && high != null)
|
||||
Range.range(low.version, low.boundType, high.version, high.boundType)
|
||||
else if (high != null)
|
||||
Range.upTo(high.version, high.boundType)
|
||||
else if (low != null)
|
||||
Range.downTo(low.version, low.boundType)
|
||||
else
|
||||
PyVersionRange(max(versionRange.lowInclusive, versionCheck.version), versionRange.highExclusive)
|
||||
Range.all()
|
||||
}
|
||||
|
||||
private fun min(a: Version?, b: Version): Version {
|
||||
return if (a == null) b else minOf(a, b)
|
||||
}
|
||||
|
||||
private fun max(a: Version?, b: Version): Version {
|
||||
return if (a == null) b else maxOf(a, b)
|
||||
}
|
||||
|
||||
internal fun serializeVersionRange(versionRange: PyVersionRange, outputStream: StubOutputStream) {
|
||||
serializeVersion(versionRange.lowInclusive, outputStream)
|
||||
serializeVersion(versionRange.highExclusive, outputStream)
|
||||
}
|
||||
|
||||
private fun serializeVersion(version: Version?, outputStream: StubOutputStream) {
|
||||
outputStream.writeBoolean(version != null)
|
||||
if (version != null) {
|
||||
outputStream.writeVarInt(version.major)
|
||||
outputStream.writeVarInt(version.minor)
|
||||
private fun deserializeEndpoint(stream: StubInputStream): Endpoint? {
|
||||
val endpointType = stream.readByte().toInt()
|
||||
val boundType = when (endpointType) {
|
||||
EndpointType.UNBOUND -> return null
|
||||
EndpointType.OPEN -> BoundType.OPEN
|
||||
EndpointType.CLOSED -> BoundType.CLOSED
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
val major = stream.readVarInt()
|
||||
val minor = stream.readVarInt()
|
||||
return Endpoint(Version(major, minor, 0), boundType)
|
||||
}
|
||||
|
||||
internal fun deserializeVersionRange(stream: StubInputStream): PyVersionRange {
|
||||
val lowInclusive = deserializeVersion(stream)
|
||||
val highExclusive = deserializeVersion(stream)
|
||||
return PyVersionRange(lowInclusive, highExclusive)
|
||||
}
|
||||
private data class Endpoint(val version: Version, val boundType: BoundType)
|
||||
|
||||
private fun deserializeVersion(stream: StubInputStream): Version? {
|
||||
val isNotNull = stream.readBoolean()
|
||||
if (isNotNull) {
|
||||
val major = stream.readVarInt()
|
||||
val minor = stream.readVarInt()
|
||||
return Version(major, minor, 0)
|
||||
}
|
||||
return null
|
||||
private object EndpointType {
|
||||
const val UNBOUND = 0
|
||||
const val OPEN = 1
|
||||
const val CLOSED = 2
|
||||
}
|
||||
@@ -5,7 +5,7 @@ if sys.version_info < (4,):
|
||||
if sys.version_info >= (3,):
|
||||
def foo(self):
|
||||
pass
|
||||
elif sys.version_info <= (2, 5):
|
||||
elif sys.version_info < (2, 5):
|
||||
def bar(self):
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import sys
|
||||
|
||||
if True:
|
||||
if sys.version_info >= (3,):
|
||||
if sys.version_info < (3, 12):
|
||||
foo = 23
|
||||
else:
|
||||
bar = -1
|
||||
@@ -0,0 +1,10 @@
|
||||
import sys
|
||||
|
||||
if True:
|
||||
if sys.version_info >= (3,):
|
||||
if sys.version_info >= (3, 10) and sys.version_info < (3, 12):
|
||||
foo = 23
|
||||
if sys.version_info < (3, 11) and (sys.version_info < (3, 5) or sys.version_info > (3, 7)):
|
||||
buz = 23
|
||||
else:
|
||||
bar = -1
|
||||
@@ -20,3 +20,6 @@ if condition1:
|
||||
s = "x"
|
||||
else:
|
||||
i = 1
|
||||
|
||||
if (sys.version_info > (2, 1) and ((sys.version_info <= (2, 2) or sys.version_info > (3, )))):
|
||||
qux = ""
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.google.common.collect.ImmutableRangeSet;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.intellij.lang.FileASTNode;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -144,11 +147,14 @@ public class PyStubsTest extends PyTestCase {
|
||||
.withChildren(
|
||||
element(PyClassStub.class, versionRange("2.5", "3.0"))
|
||||
.withChildren(
|
||||
element(PyFunctionStub.class, versionRange("3.11", "3.0")),
|
||||
element(PyFunctionStub.class, ImmutableRangeSet.of()),
|
||||
element(PyTargetExpressionStub.class, versionRange("2.5", "3.0"))
|
||||
),
|
||||
element(PyTargetExpressionStub.class, versionRange("3.12", "3.0"))
|
||||
)
|
||||
element(PyTargetExpressionStub.class, ImmutableRangeSet.of())
|
||||
),
|
||||
element(PyTargetExpressionStub.class,
|
||||
ImmutableRangeSet.unionOf(List.of(Range.openClosed(Version.parseVersion("2.1"), Version.parseVersion("2.2")),
|
||||
Range.greaterThan(Version.parseVersion("3.0")))))
|
||||
)
|
||||
.test(file.getStub());
|
||||
}
|
||||
@@ -1263,19 +1269,19 @@ public class PyStubsTest extends PyTestCase {
|
||||
}
|
||||
|
||||
private static <T extends PyVersionSpecificStub> @NotNull StubElementValidator element(@NotNull Class<T> clazz,
|
||||
@NotNull PyVersionRange versionRange) {
|
||||
@NotNull RangeSet<Version> versions) {
|
||||
return stub -> {
|
||||
assertInstanceOf(stub, clazz);
|
||||
assertEquals(versionRange, clazz.cast(stub).getVersionRange());
|
||||
assertEquals(versions, clazz.cast(stub).getVersions());
|
||||
};
|
||||
}
|
||||
|
||||
private static @NotNull PyVersionRange versionLessThan(@NotNull String version) {
|
||||
return new PyVersionRange(null, Version.parseVersion(version));
|
||||
private static @NotNull RangeSet<Version> versionLessThan(@NotNull String version) {
|
||||
return ImmutableRangeSet.of(Range.lessThan(Version.parseVersion(version)));
|
||||
}
|
||||
|
||||
private static @NotNull PyVersionRange versionRange(@NotNull String lowInclusive, @NotNull String highExclusive) {
|
||||
return new PyVersionRange(Version.parseVersion(lowInclusive), Version.parseVersion(highExclusive));
|
||||
private static @NotNull RangeSet<Version> versionRange(@NotNull String lowInclusive, @NotNull String highExclusive) {
|
||||
return ImmutableRangeSet.of(Range.closedOpen(Version.parseVersion(lowInclusive), Version.parseVersion(highExclusive)));
|
||||
}
|
||||
|
||||
private interface StubElementValidator {
|
||||
|
||||
Reference in New Issue
Block a user