mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Enable pyi-stubs for collections module (PY-23259, PY-21415, PY-17865, PY-17206)
Infer `tuple` class type when `collections.namedtuple` could not be analyzed. Infer `namedtuple` class type when `typing.NamedTuple` could not be analyzed. Create callable type for `typing.Callable`. Update ignoring `__getitem__` for ancestors, docstrings and annotations. Infer superclass collection type correctly.
This commit is contained in:
@@ -199,7 +199,8 @@ public class PyNames {
|
||||
public static final String CALLABLE_BUILTIN = "callable";
|
||||
public static final String NAMEDTUPLE = "namedtuple";
|
||||
public static final String COLLECTIONS = "collections";
|
||||
public static final String COLLECTIONS_NAMEDTUPLE = COLLECTIONS + "." + NAMEDTUPLE;
|
||||
public static final String COLLECTIONS_NAMEDTUPLE_PY2 = COLLECTIONS + "." + NAMEDTUPLE;
|
||||
public static final String COLLECTIONS_NAMEDTUPLE_PY3 = COLLECTIONS + "." + INIT + "." + NAMEDTUPLE;
|
||||
|
||||
public static final String FORMAT = "format";
|
||||
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
if (args.length == 1 && args[0] instanceof PyReferenceExpression) {
|
||||
final PyReferenceExpression target = (PyReferenceExpression)args[0];
|
||||
|
||||
pushAssertion(target, myPositive, false, context -> PyTypeParser.getTypeByName(target, "collections." + PyNames.CALLABLE, context));
|
||||
pushAssertion(target, myPositive, false, context -> new PyCallableTypeImpl(null, null));
|
||||
}
|
||||
}
|
||||
else if (node.isCalleeText(PyNames.ISSUBCLASS)) {
|
||||
|
||||
@@ -411,7 +411,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
|
||||
private static PyNamedTupleType getNamedTupleFunctionType(@NotNull PyFunction function,
|
||||
@NotNull TypeEvalContext context,
|
||||
@NotNull PyCallExpression call) {
|
||||
if (PyNames.COLLECTIONS_NAMEDTUPLE.equals(function.getQualifiedName())) {
|
||||
if (ArrayUtil.contains(function.getQualifiedName(), PyNames.COLLECTIONS_NAMEDTUPLE_PY2, PyNames.COLLECTIONS_NAMEDTUPLE_PY3)) {
|
||||
return getNamedTupleTypeFromAST(call, context, PyNamedTupleType.DefinitionLevel.NT_FUNCTION);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ object PyTypeShed {
|
||||
private val ONLY_SUPPORTED_PY2_MINOR = 7
|
||||
private val SUPPORTED_PY3_MINORS = 2..7
|
||||
val WHITE_LIST = setOf(TYPING, "six", "__builtin__", "builtins", "exceptions", "types", "datetime", "functools", "shutil", "re", "time",
|
||||
"argparse", "uuid", "threading", "signal")
|
||||
"argparse", "uuid", "threading", "signal", "collections")
|
||||
private val BLACK_LIST = setOf<String>()
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,28 +15,53 @@
|
||||
*/
|
||||
package com.jetbrains.python.codeInsight.typing
|
||||
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.inspections.PyInspectionExtension
|
||||
import com.jetbrains.python.psi.PyElement
|
||||
import com.jetbrains.python.psi.PyReferenceExpression
|
||||
import com.jetbrains.python.psi.PySubscriptionExpression
|
||||
import com.jetbrains.python.psi.PyTargetExpression
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache
|
||||
import com.jetbrains.python.psi.impl.references.PyOperatorReference
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.types.PyClassLikeType
|
||||
import com.jetbrains.python.psi.types.PyClassType
|
||||
import com.jetbrains.python.psi.types.PyType
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
|
||||
class PyTypingInspectionExtension : PyInspectionExtension() {
|
||||
|
||||
override fun ignoreUnresolvedMember(type: PyType, name: String, context: TypeEvalContext): Boolean {
|
||||
return name == PyNames.GETITEM &&
|
||||
type is PyClassLikeType &&
|
||||
type.isDefinition &&
|
||||
!isBuiltin(type) &&
|
||||
isGenericItselfOrDescendant(type, context)
|
||||
override fun ignoreUnresolvedReference(node: PyElement, reference: PsiReference, context: TypeEvalContext): Boolean {
|
||||
if (node is PySubscriptionExpression && reference is PyOperatorReference && node.referencedName == PyNames.GETITEM) {
|
||||
val operand = node.operand
|
||||
val type = context.getType(operand)
|
||||
|
||||
if (type is PyClassLikeType && type.isDefinition && isGenericItselfOrDescendant(type, context)) {
|
||||
// `true` is not returned for the cases like `typing.List[int]`
|
||||
// because these types contain builtins as a class
|
||||
if (!isBuiltin(type)) return true
|
||||
|
||||
// here is the check that current element is like `typing.List[int]`
|
||||
// but be careful: builtin collections inherit `typing.Generic` in typeshed
|
||||
if (operand is PyReferenceExpression) {
|
||||
val resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context)
|
||||
val resolveResults = operand.getReference(resolveContext).multiResolve(false)
|
||||
|
||||
if (resolveResults
|
||||
.asSequence()
|
||||
.map { it.element }
|
||||
.any { it is PyTargetExpression && PyTypingTypeProvider.BUILTIN_COLLECTION_CLASSES.containsKey(it.qualifiedName) }) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isGenericItselfOrDescendant(type: PyClassLikeType,
|
||||
context: TypeEvalContext): Boolean {
|
||||
return PyTypingTypeProvider.GENERIC_CLASSES.contains(type.classQName) ||
|
||||
type.getAncestorTypes(context).any { PyTypingTypeProvider.GENERIC_CLASSES.contains(it.classQName) }
|
||||
private fun isGenericItselfOrDescendant(type: PyClassLikeType, context: TypeEvalContext): Boolean {
|
||||
return PyTypingTypeProvider.GENERIC_CLASSES.contains(type.classQName) || PyTypingTypeProvider.isGeneric(type, context)
|
||||
}
|
||||
|
||||
private fun isBuiltin(type: PyClassLikeType): Boolean {
|
||||
|
||||
@@ -69,6 +69,14 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
public static final String TYPE = "typing.Type";
|
||||
public static final String ANY = "typing.Any";
|
||||
private static final String CALLABLE = "typing.Callable";
|
||||
private static final String LIST = "typing.List";
|
||||
private static final String DICT = "typing.Dict";
|
||||
private static final String DEFAULT_DICT = "typing.DefaultDict";
|
||||
private static final String SET = "typing.Set";
|
||||
private static final String FROZEN_SET = "typing.FrozenSet";
|
||||
private static final String COUNTER = "typing.Counter";
|
||||
private static final String DEQUE = "typing.Deque";
|
||||
private static final String TUPLE = "typing.Tuple";
|
||||
private static final String CLASSVAR = "typing.ClassVar";
|
||||
|
||||
public static final String NAMEDTUPLE_SIMPLE = "NamedTuple";
|
||||
@@ -85,12 +93,18 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
|
||||
public static final Pattern TYPE_COMMENT_PATTERN = Pattern.compile("# *type: *(.*)");
|
||||
|
||||
private static final ImmutableMap<String, String> COLLECTION_CLASSES = ImmutableMap.<String, String>builder()
|
||||
.put("typing.List", "list")
|
||||
.put("typing.Dict", "dict")
|
||||
.put("typing.Set", PyNames.SET)
|
||||
.put("typing.FrozenSet", "frozenset")
|
||||
.put("typing.Tuple", PyNames.TUPLE)
|
||||
public static final ImmutableMap<String, String> BUILTIN_COLLECTION_CLASSES = ImmutableMap.<String, String>builder()
|
||||
.put(LIST, "list")
|
||||
.put(DICT, "dict")
|
||||
.put(SET, PyNames.SET)
|
||||
.put(FROZEN_SET, "frozenset")
|
||||
.put(TUPLE, PyNames.TUPLE)
|
||||
.build();
|
||||
|
||||
private static final ImmutableMap<String, String> COLLECTIONS_CLASSES = ImmutableMap.<String, String>builder()
|
||||
.put(DEFAULT_DICT, "collections.DefaultDict")
|
||||
.put(COUNTER, "collections.Counter")
|
||||
.put(DEQUE, "collections.Deque")
|
||||
.build();
|
||||
|
||||
public static final ImmutableMap<String, String> TYPING_COLLECTION_CLASSES = ImmutableMap.<String, String>builder()
|
||||
@@ -116,17 +130,21 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
.add("typing.Any")
|
||||
.add("typing.TypeVar")
|
||||
.add(GENERIC)
|
||||
.add("typing.Tuple")
|
||||
.add(TUPLE)
|
||||
.add(CALLABLE)
|
||||
.add("typing.Type")
|
||||
.add("typing.no_type_check")
|
||||
.add("typing.Union")
|
||||
.add("typing.Optional")
|
||||
.add("typing.List")
|
||||
.add("typing.Dict")
|
||||
.add("typing.DefaultDict")
|
||||
.add("typing.Set")
|
||||
.add(LIST)
|
||||
.add(DICT)
|
||||
.add(DEFAULT_DICT)
|
||||
.add(SET)
|
||||
.add(FROZEN_SET)
|
||||
.add(PROTOCOL)
|
||||
.add(CLASSVAR)
|
||||
.add(COUNTER)
|
||||
.add(DEQUE)
|
||||
.build();
|
||||
|
||||
@Nullable
|
||||
@@ -144,6 +162,12 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
return createTypingProtocolType();
|
||||
}
|
||||
}
|
||||
// Check for the exact name in advance for performance reasons
|
||||
if ("Callable".equals(referenceExpression.getName())) {
|
||||
if (resolveToQualifiedNames(referenceExpression, context).contains(CALLABLE)) {
|
||||
return createTypingCallableType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -328,6 +352,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
return new PyCustomType(PROTOCOL, null, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyType createTypingCallableType() {
|
||||
return new PyCallableTypeImpl(null, null);
|
||||
}
|
||||
|
||||
private static boolean omitFirstParamInTypeComment(@NotNull PyFunction func, @NotNull PyFunctionTypeAnnotation annotation) {
|
||||
return func.getContainingClass() != null && func.getModifier() != PyFunction.Modifier.STATICMETHOD &&
|
||||
annotation.getParameterTypeList().getParameterTypes().size() < func.getParameterList().getParameters().length;
|
||||
@@ -441,6 +470,16 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
if (PROTOCOL.equals(target.getQualifiedName())) {
|
||||
return createTypingProtocolType();
|
||||
}
|
||||
// Depends on typing.Callable defined as a target expression
|
||||
if (CALLABLE.equals(target.getQualifiedName())) {
|
||||
return createTypingCallableType();
|
||||
}
|
||||
|
||||
final PyType collection = getCollection(target, context);
|
||||
if (collection instanceof PyInstantiableType) {
|
||||
return ((PyInstantiableType)collection).toClass();
|
||||
}
|
||||
|
||||
final Ref<PyType> annotatedType = getTypeFromTargetExpressionAnnotation(target, context);
|
||||
if (annotatedType != null) {
|
||||
return annotatedType.get();
|
||||
@@ -563,8 +602,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<PyType, PyType> getGenericSubstitutions(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
|
||||
final Context ctx = new Context(context);
|
||||
if (!isGeneric(cls, ctx)) {
|
||||
if (!isGeneric(cls, context)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
final Map<PyType, PyType> results = new HashMap<>();
|
||||
@@ -577,6 +615,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
results.putAll(superSubstitutions);
|
||||
}
|
||||
if (superClass != null) {
|
||||
final Context ctx = new Context(context);
|
||||
final List<PyType> superGenerics = collectGenericTypes(superClass, ctx);
|
||||
final List<PyExpression> indices = subscriptionExpr != null ? getSubscriptionIndices(subscriptionExpr) : Collections.emptyList();
|
||||
for (int i = 0; i < superGenerics.size(); i++) {
|
||||
@@ -649,7 +688,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
|
||||
@NotNull
|
||||
private static List<PyType> collectGenericTypes(@NotNull PyClass cls, @NotNull Context context) {
|
||||
if (!isGeneric(cls, context)) {
|
||||
if (!isGeneric(cls, context.getTypeContext())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final TypeEvalContext typeEvalContext = context.getTypeContext();
|
||||
@@ -667,8 +706,8 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static boolean isGeneric(@NotNull PyClass cls, @NotNull Context context) {
|
||||
for (PyClassLikeType ancestor : cls.getAncestorTypes(context.getTypeContext())) {
|
||||
public static boolean isGeneric(@NotNull PyWithAncestors descendant, @NotNull TypeEvalContext context) {
|
||||
for (PyClassLikeType ancestor : descendant.getAncestorTypes(context)) {
|
||||
if (ancestor != null && GENERIC_CLASSES.contains(ancestor.getClassQName())) {
|
||||
return true;
|
||||
}
|
||||
@@ -723,9 +762,9 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
if (parameterizedType != null) {
|
||||
return Ref.create(parameterizedType);
|
||||
}
|
||||
final PyType builtinCollection = getBuiltinCollection(resolved, context.getTypeContext());
|
||||
if (builtinCollection != null) {
|
||||
return Ref.create(builtinCollection);
|
||||
final PyType collection = getCollection(resolved, context.getTypeContext());
|
||||
if (collection != null) {
|
||||
return Ref.create(collection);
|
||||
}
|
||||
final PyType genericType = getGenericTypeFromTypeVar(resolved, context);
|
||||
if (genericType != null) {
|
||||
@@ -1019,10 +1058,16 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType getBuiltinCollection(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
|
||||
final String collectionName = getQualifiedName(element);
|
||||
final String builtinName = COLLECTION_CLASSES.get(collectionName);
|
||||
return builtinName != null ? PyTypeParser.getTypeByName(element, builtinName, context) : null;
|
||||
private static PyType getCollection(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
|
||||
final String typingName = getQualifiedName(element);
|
||||
|
||||
final String builtinName = BUILTIN_COLLECTION_CLASSES.get(typingName);
|
||||
if (builtinName != null) return PyTypeParser.getTypeByName(element, builtinName, context);
|
||||
|
||||
final String collectionName = COLLECTIONS_CLASSES.get(typingName);
|
||||
if (collectionName != null) return PyTypeParser.getTypeByName(element, collectionName, context);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -148,6 +148,7 @@ public class PyiUtil {
|
||||
return PyUtil.as(PyResolveImportUtil.resolveQualifiedName(name, context)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.map(PyUtil::turnDirIntoInit)
|
||||
.orElse(null), PyiFile.class);
|
||||
}
|
||||
|
||||
@@ -161,6 +162,7 @@ public class PyiUtil {
|
||||
return PyUtil.as(PyResolveImportUtil.resolveQualifiedName(name, context)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.map(PyUtil::turnDirIntoInit)
|
||||
.orElse(null), PyFile.class);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -16,4 +16,6 @@ class Z(Y[T]):
|
||||
pass
|
||||
|
||||
|
||||
Z[int]
|
||||
a: Z[int]
|
||||
Alias = Z[int]
|
||||
Z[int]()
|
||||
@@ -0,0 +1,4 @@
|
||||
from b import A
|
||||
|
||||
A().append()
|
||||
# <ref>
|
||||
@@ -0,0 +1,4 @@
|
||||
from typing import List
|
||||
|
||||
class A(List[int]):
|
||||
pass
|
||||
@@ -0,0 +1,5 @@
|
||||
from typing import List
|
||||
class A(List[int]):
|
||||
pass
|
||||
A().append()
|
||||
# <ref>
|
||||
@@ -0,0 +1,4 @@
|
||||
from typing import NamedTuple
|
||||
from b import fields
|
||||
|
||||
nt = NamedTuple("name", **fields)
|
||||
@@ -0,0 +1,169 @@
|
||||
def test_namedtuple():
|
||||
from collections import namedtuple
|
||||
|
||||
assert namedtuple("Point", "x y")(1, 2).x == 1
|
||||
assert namedtuple(u"Point", u"x y", verbose=False, rename=False)(1, 2).x == 1
|
||||
assert namedtuple(b"Point", b"x y", verbose=True, rename=True)(1, 2).x == 1
|
||||
|
||||
assert namedtuple("Point", ["x", "y"])(1, 2).x == 1
|
||||
assert namedtuple(u"Point", [u"x", u"y"])(1, 2).x == 1
|
||||
assert namedtuple(b"Point", [b"x", b"y"])(1, 2).x == 1
|
||||
|
||||
assert namedtuple("Point", [b"x", u"y"])(1, 2).x == 1
|
||||
|
||||
Point = namedtuple('Point', 'x y')
|
||||
p = Point(1, 2)
|
||||
|
||||
assert p == Point(1, 2)
|
||||
|
||||
assert p._replace(y=3.14).y == 3.14
|
||||
assert p._asdict()['x'] == 1
|
||||
assert p._fields == ('x', 'y')
|
||||
|
||||
assert p == (1, 2)
|
||||
assert (p.x, p.y) == (1, 2)
|
||||
assert p[0] + p[1] == 3
|
||||
assert p.index(1) == 0
|
||||
|
||||
assert Point._make([1, 3.14]).y == 3.14
|
||||
|
||||
|
||||
def test_deque():
|
||||
from collections import deque
|
||||
|
||||
d = deque([2])
|
||||
assert list(deque([1, 2, 3])) == [1, 2, 3]
|
||||
assert list(deque([1, 2, 3], 2)) == [2, 3]
|
||||
|
||||
assert deque([1, 2, 3]).maxlen is None
|
||||
assert deque([1, 2, 3], 2).maxlen == 2
|
||||
|
||||
d.append(3)
|
||||
d.appendleft(1)
|
||||
assert list(d) == [1, 2, 3]
|
||||
|
||||
d.clear()
|
||||
assert len(d) == 0
|
||||
|
||||
d.extend([1, 2, 3])
|
||||
d.extendleft([5, 3, 1])
|
||||
assert d.count(3) == 2
|
||||
assert list(d) == [1, 3, 5, 1, 2, 3]
|
||||
|
||||
assert d.pop() == 3
|
||||
assert d.popleft() == 1
|
||||
d.remove(5)
|
||||
assert list(d) == [3, 1, 2]
|
||||
|
||||
d.reverse()
|
||||
assert list(d) == [2, 1, 3]
|
||||
d.rotate(1)
|
||||
assert list(d) == [3, 2, 1]
|
||||
d.rotate(-2)
|
||||
assert list(d) == [1, 3, 2]
|
||||
|
||||
assert len(d) == 3
|
||||
assert 3 in d
|
||||
assert d[1] == 3
|
||||
d[1] = 4
|
||||
assert list(d) == [1, 4, 2]
|
||||
|
||||
assert list(reversed(d)) == [2, 4, 1]
|
||||
|
||||
|
||||
def test_counter():
|
||||
from collections import Counter
|
||||
|
||||
c = Counter()
|
||||
assert Counter("abc") == {"a": 1, "b": 1, "c": 1}
|
||||
assert Counter({"a": 1, "b": 2, "c": 3}) == {"a": 1, "b": 2, "c": 3}
|
||||
assert Counter(a=1, b=2, c=3) == {"a": 1, "b": 2, "c": 3}
|
||||
|
||||
c["abc"] = 1
|
||||
c["def"] = 2
|
||||
assert c == {"abc": 1, "def": 2}
|
||||
|
||||
c["def"] = 0
|
||||
assert c == {"abc": 1, "def": 0}
|
||||
del c["def"]
|
||||
|
||||
assert c["ghi"] == 0
|
||||
|
||||
c.update({"ghi": 2})
|
||||
assert list(c.elements()) == ["abc", "ghi", "ghi"]
|
||||
|
||||
c.update(["a", "a", "b", "a", "a", "a", "b", "a"])
|
||||
assert c == {"abc": 1, "ghi": 2, "a": 6, "b": 2}
|
||||
|
||||
c.update(a=-3, b=-1)
|
||||
assert c == {"abc": 1, "ghi": 2, "a": 3, "b": 1}
|
||||
|
||||
assert c.most_common(2) == [("a", 3), ("ghi", 2)]
|
||||
assert c.most_common() == [("a", 3), ("ghi", 2), ("abc", 1), ("b", 1)]
|
||||
c.subtract({"abc": 1, "ghi": -2, "a": 3, "b": -1})
|
||||
assert c == {"abc": 0, "ghi": 4, "a": 0, "b": 2}
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
d = Counter(a=1, b=2)
|
||||
assert c + d == Counter({'a': 4, 'b': 3})
|
||||
assert c - d == Counter({'a': 2})
|
||||
assert c & d == Counter({'a': 1, 'b': 1})
|
||||
assert c | d == Counter({'a': 3, 'b': 2})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c += d
|
||||
assert c == Counter({'a': 4, 'b': 3})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c -= d
|
||||
assert c == Counter({'a': 2})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c &= d
|
||||
assert c == Counter({'a': 1, 'b': 1})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c |= d
|
||||
assert c == Counter({'a': 3, 'b': 2})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c.subtract(["a", "b"])
|
||||
assert c == {"a": 2, "b": 0}
|
||||
|
||||
|
||||
def test_ordered_dict():
|
||||
from collections import OrderedDict
|
||||
|
||||
od = OrderedDict([("a", 1), ("b", 2), ("c", 3), ("d", 4)])
|
||||
assert od.popitem() == ("d", 4)
|
||||
assert od == OrderedDict([("a", 1), ("b", 2), ("c", 3)])
|
||||
assert od.popitem(last=False) == ("a", 1)
|
||||
assert od == OrderedDict([("b", 2), ("c", 3)])
|
||||
assert od.popitem(last=True) == ("c", 3)
|
||||
assert od == OrderedDict([("b", 2)])
|
||||
assert od == {"b": 2}
|
||||
|
||||
|
||||
def test_defaultdict():
|
||||
from collections import defaultdict
|
||||
|
||||
assert defaultdict() == {}
|
||||
assert defaultdict(k1=1, k2=2) == {"k1": 1, "k2": 2}
|
||||
|
||||
assert defaultdict(lambda: 1) == {}
|
||||
assert defaultdict(lambda: 2, {"k1": 1, "k2": 2}) == {"k1": 1, "k2": 2}
|
||||
assert defaultdict(lambda: 3, [("k1", 1), ("k2", 2)]) == {"k1": 1, "k2": 2}
|
||||
|
||||
assert defaultdict(None) == {}
|
||||
assert defaultdict(None, {"k1": 1, "k2": 2}) == {"k1": 1, "k2": 2}
|
||||
assert defaultdict(None, [("k1", 1), ("k2", 2)]) == {"k1": 1, "k2": 2}
|
||||
|
||||
assert defaultdict(lambda: 4).__missing__("key") == 4
|
||||
|
||||
|
||||
def test_abc():
|
||||
from collections import (Container, Hashable, Iterable, Iterator, Sized, Callable, Sequence, MutableSequence, Set,
|
||||
MutableSet, Mapping, MutableMapping, MappingView, ItemsView, KeysView, ValuesView)
|
||||
|
||||
assert [Container, Hashable, Iterable, Iterator, Sized, Callable, Sequence, MutableSequence, Set, MutableSet,
|
||||
Mapping, MutableMapping, MappingView, ItemsView, KeysView, ValuesView]
|
||||
@@ -1,14 +0,0 @@
|
||||
def test_namedtuple():
|
||||
from collections import namedtuple
|
||||
|
||||
Point = namedtuple('Point', 'x y')
|
||||
p = Point(1, 2)
|
||||
|
||||
assert p == Point(1, 2)
|
||||
assert p == (1, 2)
|
||||
assert p._replace(y=3.14).y == 3.14
|
||||
assert p._asdict()['x'] == 1
|
||||
assert (p.x, p.y) == (1, 2)
|
||||
assert p[0] + p[1] == 3
|
||||
assert p.index(1) == 0
|
||||
assert Point._make([1, 3.14]).y == 3.14
|
||||
@@ -0,0 +1,242 @@
|
||||
def test_namedtuple():
|
||||
from collections import namedtuple
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 6):
|
||||
assert namedtuple("Point", "x y", verbose=True, rename=True, module="m1")(1, 2).x == 1
|
||||
assert namedtuple(u"Point", u"x y", verbose=False, rename=False, module="m2")(1, 2).x == 1
|
||||
assert namedtuple(u"Point", u"x y", verbose=False, rename=False, module=None)(1, 2).x == 1
|
||||
|
||||
assert namedtuple("Point", ["x", "y"])(1, 2).x == 1
|
||||
assert namedtuple(u"Point", [u"x", u"y"])(1, 2).x == 1
|
||||
else:
|
||||
assert namedtuple("Point", "x y", verbose=True, rename=True)(1, 2).x == 1
|
||||
assert namedtuple(u"Point", u"x y", verbose=False, rename=False)(1, 2).x == 1
|
||||
|
||||
assert namedtuple("Point", ["x", "y"], True, True)(1, 2).x == 1
|
||||
assert namedtuple(u"Point", [u"x", u"y"], False, False)(1, 2).x == 1
|
||||
|
||||
Point = namedtuple('Point', 'x y')
|
||||
p = Point(1, 2)
|
||||
|
||||
assert p == Point(1, 2)
|
||||
|
||||
assert p._replace(y=3.14).y == 3.14
|
||||
assert p._asdict()['x'] == 1
|
||||
assert p._fields == ('x', 'y')
|
||||
assert p._source is not None
|
||||
|
||||
assert p == (1, 2)
|
||||
assert (p.x, p.y) == (1, 2)
|
||||
assert p[0] + p[1] == 3
|
||||
assert p.index(1) == 0
|
||||
|
||||
assert Point._make([1, 3.14]).y == 3.14
|
||||
|
||||
|
||||
def test_deque():
|
||||
from collections import deque
|
||||
import sys
|
||||
|
||||
d = deque([2])
|
||||
assert list(deque([1, 2, 3])) == [1, 2, 3]
|
||||
assert list(deque([1, 2, 3], 2)) == [2, 3]
|
||||
|
||||
assert deque([1, 2, 3]).maxlen is None
|
||||
assert deque([1, 2, 3], 2).maxlen == 2
|
||||
|
||||
d.append(3)
|
||||
d.appendleft(1)
|
||||
assert list(d) == [1, 2, 3]
|
||||
|
||||
d.clear()
|
||||
assert len(d) == 0
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
copy = d.copy()
|
||||
assert copy == d
|
||||
assert copy is not d
|
||||
|
||||
d.extend([1, 2, 3])
|
||||
d.extendleft([5, 3, 1])
|
||||
assert d.count(3) == 2
|
||||
assert list(d) == [1, 3, 5, 1, 2, 3]
|
||||
|
||||
assert d.pop() == 3
|
||||
assert d.popleft() == 1
|
||||
d.remove(5)
|
||||
assert list(d) == [3, 1, 2]
|
||||
|
||||
d.reverse()
|
||||
assert list(d) == [2, 1, 3]
|
||||
d.rotate(1)
|
||||
assert list(d) == [3, 2, 1]
|
||||
d.rotate(-2)
|
||||
assert list(d) == [1, 3, 2]
|
||||
|
||||
assert len(d) == 3
|
||||
assert 3 in d
|
||||
assert d[1] == 3
|
||||
d[1] = 4
|
||||
assert list(d) == [1, 4, 2]
|
||||
|
||||
assert list(reversed(d)) == [2, 4, 1]
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
d.insert(len(d), 4)
|
||||
assert list(d) == [1, 4, 2, 4]
|
||||
|
||||
assert d.index(4) == 1
|
||||
assert d.index(4, 2) == 3
|
||||
assert d.index(4, 2, len(d)) == 3
|
||||
|
||||
assert d + deque([5, 6]) == deque(list(d) + [5, 6])
|
||||
assert d * 2 == deque([1, 4, 2, 4, 1, 4, 2, 4])
|
||||
d *= 2
|
||||
assert list(d) == [1, 4, 2, 4, 1, 4, 2, 4]
|
||||
|
||||
|
||||
def test_chain_map():
|
||||
from collections import ChainMap
|
||||
|
||||
ChainMap()
|
||||
cm = ChainMap({"a": 1, "b": 2})
|
||||
|
||||
assert cm.maps is not None
|
||||
|
||||
assert cm.new_child().maps is not None
|
||||
assert cm.new_child({"c": 3}).maps is not None
|
||||
|
||||
assert cm.parents.maps is not None
|
||||
|
||||
cm["d"] = 4
|
||||
del cm["a"]
|
||||
assert cm["a"] is None
|
||||
assert cm["b"] == 2
|
||||
assert cm["d"] == 4
|
||||
assert list(iter(cm)) == ["b", "d", "c"]
|
||||
assert len(cm) == 3
|
||||
|
||||
|
||||
def test_counter():
|
||||
from collections import Counter
|
||||
|
||||
c = Counter()
|
||||
assert Counter("abc") == {"a": 1, "b": 1, "c": 1}
|
||||
assert Counter({"a": 1, "b": 2, "c": 3}) == {"a": 1, "b": 2, "c": 3}
|
||||
assert Counter(a=1, b=2, c=3) == {"a": 1, "b": 2, "c": 3}
|
||||
|
||||
c["abc"] = 1
|
||||
c["def"] = 2
|
||||
assert c == {"abc": 1, "def": 2}
|
||||
|
||||
c["def"] = 0
|
||||
assert c == {"abc": 1, "def": 0}
|
||||
del c["def"]
|
||||
|
||||
assert c["ghi"] == 0
|
||||
|
||||
c.update({"ghi": 2})
|
||||
assert list(c.elements()) == ["abc", "ghi", "ghi"]
|
||||
|
||||
c.update(["a", "a", "b", "a", "a", "a", "b", "a"])
|
||||
assert c == {"abc": 1, "ghi": 2, "a": 6, "b": 2}
|
||||
|
||||
c.update(a=-3, b=-1)
|
||||
assert c == {"abc": 1, "ghi": 2, "a": 3, "b": 1}
|
||||
|
||||
assert c.most_common(2) == [("a", 3), ("ghi", 2)]
|
||||
assert c.most_common() == [("a", 3), ("ghi", 2), ("abc", 1), ("b", 1)]
|
||||
c.subtract({"abc": 1, "ghi": -2, "a": 3, "b": -1})
|
||||
assert c == {"abc": 0, "ghi": 4, "a": 0, "b": 2}
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
d = Counter(a=1, b=2)
|
||||
assert c + d == Counter({'a': 4, 'b': 3})
|
||||
assert c - d == Counter({'a': 2})
|
||||
assert c & d == Counter({'a': 1, 'b': 1})
|
||||
assert c | d == Counter({'a': 3, 'b': 2})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c += d
|
||||
assert c == Counter({'a': 4, 'b': 3})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c -= d
|
||||
assert c == Counter({'a': 2})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c &= d
|
||||
assert c == Counter({'a': 1, 'b': 1})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c |= d
|
||||
assert c == Counter({'a': 3, 'b': 2})
|
||||
|
||||
c = Counter(a=3, b=1)
|
||||
c.subtract(["a", "b"])
|
||||
assert c == {"a": 2, "b": 0}
|
||||
|
||||
c = Counter(a=2, b=-4)
|
||||
assert +c == {"a": 2}
|
||||
assert -c == {"b": 4}
|
||||
|
||||
|
||||
def test_ordered_dict():
|
||||
from collections import OrderedDict
|
||||
|
||||
od = OrderedDict([("a", 1), ("b", 2), ("c", 3), ("d", 4)])
|
||||
assert od.popitem() == ("d", 4)
|
||||
assert od == OrderedDict([("a", 1), ("b", 2), ("c", 3)])
|
||||
assert od.popitem(last=False) == ("a", 1)
|
||||
assert od == OrderedDict([("b", 2), ("c", 3)])
|
||||
assert od.popitem(last=True) == ("c", 3)
|
||||
assert od == OrderedDict([("b", 2)])
|
||||
assert od == {"b": 2}
|
||||
|
||||
od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
|
||||
od.move_to_end("a")
|
||||
assert list(od.keys()) == ["b", "c", "a"]
|
||||
od.move_to_end("c", last=True)
|
||||
assert list(od.keys()) == ["b", "a", "c"]
|
||||
od.move_to_end("a", last=False)
|
||||
assert list(od.keys()) == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_defaultdict():
|
||||
from collections import defaultdict
|
||||
|
||||
assert defaultdict() == {}
|
||||
assert defaultdict(k1=1, k2=2) == {"k1": 1, "k2": 2}
|
||||
|
||||
assert defaultdict(lambda: 1) == {}
|
||||
assert defaultdict(lambda: 2, {"k1": 1, "k2": 2}) == {"k1": 1, "k2": 2}
|
||||
assert defaultdict(lambda: 3, [("k1", 1), ("k2", 2)]) == {"k1": 1, "k2": 2}
|
||||
|
||||
assert defaultdict(None) == {}
|
||||
assert defaultdict(None, {"k1": 1, "k2": 2}) == {"k1": 1, "k2": 2}
|
||||
assert defaultdict(None, [("k1", 1), ("k2", 2)]) == {"k1": 1, "k2": 2}
|
||||
|
||||
assert defaultdict(lambda: 4).__missing__("key") == 4
|
||||
|
||||
|
||||
def test_abc():
|
||||
from collections import (Container, Hashable, Iterable, Iterator, Reversible, Generator, Sized, Callable,
|
||||
Collection, Sequence, MutableSequence, ByteString, Set, MutableSet, Mapping,
|
||||
MutableMapping, MappingView, ItemsView, KeysView, ValuesView, Awaitable, Coroutine,
|
||||
AsyncIterable, AsyncIterator, AsyncGenerator)
|
||||
|
||||
assert [Container, Hashable, Iterable, Iterator, Reversible, Generator, Sized, Callable,
|
||||
Collection, Sequence, MutableSequence, ByteString, Set, MutableSet, Mapping,
|
||||
MutableMapping, MappingView, ItemsView, KeysView, ValuesView, Awaitable, Coroutine,
|
||||
AsyncIterable, AsyncIterator, AsyncGenerator]
|
||||
|
||||
from collections.abc import (Container, Hashable, Iterable, Iterator, Reversible, Generator, Sized, Callable,
|
||||
Collection, Sequence, MutableSequence, ByteString, Set, MutableSet, Mapping,
|
||||
MutableMapping, MappingView, ItemsView, KeysView, ValuesView, Awaitable, Coroutine,
|
||||
AsyncIterable, AsyncIterator, AsyncGenerator)
|
||||
|
||||
assert [Container, Hashable, Iterable, Iterator, Reversible, Generator, Sized, Callable,
|
||||
Collection, Sequence, MutableSequence, ByteString, Set, MutableSet, Mapping,
|
||||
MutableMapping, MappingView, ItemsView, KeysView, ValuesView, Awaitable, Coroutine,
|
||||
AsyncIterable, AsyncIterator, AsyncGenerator]
|
||||
@@ -1236,4 +1236,15 @@ public class PyResolveTest extends PyResolveTestCase {
|
||||
final PyFunction function = assertInstanceOf(doResolve(), PyFunction.class);
|
||||
assertEquals(4, function.getTextOffset());
|
||||
}
|
||||
|
||||
// PY-23259
|
||||
public void testTypingListInheritor() {
|
||||
assertResolvesTo(PyFunction.class, "append");
|
||||
}
|
||||
|
||||
// PY-23259
|
||||
public void testImportedTypingListInheritor() {
|
||||
myFixture.copyDirectoryToProject("resolve/" + getTestName(false), "");
|
||||
assertResolvesTo(PyFunction.class, "append");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,15 @@ import com.intellij.psi.stubs.StubIndex;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType;
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.impl.PyFileImpl;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
|
||||
import com.jetbrains.python.psi.stubs.*;
|
||||
import com.jetbrains.python.psi.types.PyCallableType;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.toolbox.Maybe;
|
||||
@@ -28,6 +33,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* @author max
|
||||
@@ -464,12 +470,12 @@ public class PyStubsTest extends PyTestCase {
|
||||
doTestNamedTupleArguments();
|
||||
}
|
||||
|
||||
public void _testImportedNamedTupleName() {
|
||||
doTestUnsupportedNamedTuple();
|
||||
public void testImportedNamedTupleName() {
|
||||
doTestUnsupportedNamedTuple(getTestFile());
|
||||
}
|
||||
|
||||
public void _testImportedNamedTupleFields() {
|
||||
doTestUnsupportedNamedTuple();
|
||||
public void testImportedNamedTupleFields() {
|
||||
doTestUnsupportedNamedTuple(getTestFile());
|
||||
}
|
||||
|
||||
public void testFullyQualifiedTypingNamedTuple() {
|
||||
@@ -512,12 +518,12 @@ public class PyStubsTest extends PyTestCase {
|
||||
doTestTypingNamedTupleArguments();
|
||||
}
|
||||
|
||||
public void _testImportedTypingNamedTupleName() {
|
||||
doTestUnsupportedNamedTuple();
|
||||
public void testImportedTypingNamedTupleName() {
|
||||
doTestUnsupportedTypingNamedTuple(getTestFile());
|
||||
}
|
||||
|
||||
public void _testImportedTypingNamedTupleFields() {
|
||||
doTestUnsupportedNamedTuple();
|
||||
public void testImportedTypingNamedTupleFields() {
|
||||
doTestUnsupportedTypingNamedTuple(getTestFile());
|
||||
}
|
||||
|
||||
public void testFullyQualifiedTypingNamedTupleKwargs() {
|
||||
@@ -552,12 +558,12 @@ public class PyStubsTest extends PyTestCase {
|
||||
doTestTypingNamedTupleArguments();
|
||||
}
|
||||
|
||||
public void _testImportedTypingNamedTupleKwargsName() {
|
||||
doTestUnsupportedNamedTuple();
|
||||
public void testImportedTypingNamedTupleKwargsName() {
|
||||
doTestUnsupportedTypingNamedTuple(getTestFile());
|
||||
}
|
||||
|
||||
public void _testImportedTypingNamedTupleKwargsFields() {
|
||||
doTestUnsupportedNamedTuple();
|
||||
public void testImportedTypingNamedTupleKwargsFields() {
|
||||
doTestUnsupportedTypingNamedTuple(getTestFile());
|
||||
}
|
||||
|
||||
private void doTestNamedTuple(@NotNull QualifiedName expectedCalleeName) {
|
||||
@@ -600,7 +606,32 @@ public class PyStubsTest extends PyTestCase {
|
||||
doTestNamedTuple(expectedName, expectedFieldsNames, expectedFieldsTypes, typeFromAst);
|
||||
}
|
||||
|
||||
private void doTestUnsupportedNamedTuple() {
|
||||
private void doTestUnsupportedNamedTuple(@NotNull PsiElement anchor) {
|
||||
doTestUnsupportedNT(
|
||||
(typeFromAst, context) -> {
|
||||
assertInstanceOf(typeFromAst, PyCallableType.class);
|
||||
|
||||
final PyType returnType = ((PyCallableType)typeFromAst).getReturnType(context);
|
||||
assertEquals(PyBuiltinCache.getInstance(anchor).getTupleType(), returnType);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void doTestUnsupportedTypingNamedTuple(@NotNull PsiElement anchor) {
|
||||
final QualifiedName typingNTName = QualifiedName.fromDottedString(PyTypingTypeProvider.NAMEDTUPLE);
|
||||
|
||||
final PsiElement member = PyResolveImportUtil.resolveTopLevelMember(typingNTName, PyResolveImportUtil.fromFoothold(anchor));
|
||||
assertInstanceOf(member, PyClass.class);
|
||||
|
||||
doTestUnsupportedNT(
|
||||
(typeFromAst, context) -> {
|
||||
assertInstanceOf(typeFromAst, PyClassType.class);
|
||||
assertEquals(member, ((PyClassType)typeFromAst).getPyClass());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void doTestUnsupportedNT(@NotNull BiConsumer<PyType, TypeEvalContext> typeFromAstChecker) {
|
||||
final PyFile file = getTestFile();
|
||||
|
||||
final PyTargetExpression attribute = file.findTopLevelAttribute("nt");
|
||||
@@ -613,8 +644,8 @@ public class PyStubsTest extends PyTestCase {
|
||||
final FileASTNode astNode = file.getNode();
|
||||
assertNotNull(astNode);
|
||||
|
||||
final PyType typeFromAst = TypeEvalContext.userInitiated(myFixture.getProject(), file).getType(attribute);
|
||||
assertNull(typeFromAst);
|
||||
final TypeEvalContext context = TypeEvalContext.userInitiated(myFixture.getProject(), file);
|
||||
typeFromAstChecker.accept(context.getType(attribute), context);
|
||||
}
|
||||
|
||||
private static void doTestNamedTuple(@NotNull String expectedName,
|
||||
|
||||
@@ -1410,8 +1410,7 @@ public class PyTypeTest extends PyTestCase {
|
||||
}
|
||||
|
||||
// PY-20797
|
||||
// TODO: Enable after switching to collections stub from Typeshed
|
||||
public void _testValueOfEmptyDefaultDict() {
|
||||
public void testValueOfEmptyDefaultDict() {
|
||||
doTest("list",
|
||||
"from collections import defaultdict\n" +
|
||||
"expr = defaultdict(lambda: [])['x']\n");
|
||||
|
||||
Reference in New Issue
Block a user