diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index 1caa40a3035b..6cce186ad22b 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -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"; diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java index d197aaf6bba4..f40f894b32b7 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java @@ -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)) { diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index cc777297ecd9..0c6ab0917ed3 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -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); } diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt b/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt index 2c4557c4de42..43216913f465 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt @@ -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() /** diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingInspectionExtension.kt b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingInspectionExtension.kt index 01c4e61997a1..2003bd9ef8ff 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingInspectionExtension.kt +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingInspectionExtension.kt @@ -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 { diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index 1266ac2321fc..3b99073bd716 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -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 COLLECTION_CLASSES = ImmutableMap.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 BUILTIN_COLLECTION_CLASSES = ImmutableMap.builder() + .put(LIST, "list") + .put(DICT, "dict") + .put(SET, PyNames.SET) + .put(FROZEN_SET, "frozenset") + .put(TUPLE, PyNames.TUPLE) + .build(); + + private static final ImmutableMap COLLECTIONS_CLASSES = ImmutableMap.builder() + .put(DEFAULT_DICT, "collections.DefaultDict") + .put(COUNTER, "collections.Counter") + .put(DEQUE, "collections.Deque") .build(); public static final ImmutableMap TYPING_COLLECTION_CLASSES = ImmutableMap.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 annotatedType = getTypeFromTargetExpressionAnnotation(target, context); if (annotatedType != null) { return annotatedType.get(); @@ -563,8 +602,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { @NotNull @Override public Map 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 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 superGenerics = collectGenericTypes(superClass, ctx); final List 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 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 diff --git a/python/src/com/jetbrains/python/pyi/PyiUtil.java b/python/src/com/jetbrains/python/pyi/PyiUtil.java index 344cfc98040e..0cd0ce85679b 100644 --- a/python/src/com/jetbrains/python/pyi/PyiUtil.java +++ b/python/src/com/jetbrains/python/pyi/PyiUtil.java @@ -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); } diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/typingGenericIndirectInheritorGetItem.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/typingGenericIndirectInheritorGetItem.py index 9ac4f278ea56..9aae906f22b8 100644 --- a/python/testData/inspections/PyUnresolvedReferencesInspection3K/typingGenericIndirectInheritorGetItem.py +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/typingGenericIndirectInheritorGetItem.py @@ -16,4 +16,6 @@ class Z(Y[T]): pass -Z[int] \ No newline at end of file +a: Z[int] +Alias = Z[int] +Z[int]() \ No newline at end of file diff --git a/python/testData/resolve/ImportedTypingListInheritor.py b/python/testData/resolve/ImportedTypingListInheritor.py new file mode 100644 index 000000000000..1244f1fdbea1 --- /dev/null +++ b/python/testData/resolve/ImportedTypingListInheritor.py @@ -0,0 +1,4 @@ +from b import A + +A().append() +# \ No newline at end of file diff --git a/python/testData/resolve/ImportedTypingListInheritor/b.py b/python/testData/resolve/ImportedTypingListInheritor/b.py new file mode 100644 index 000000000000..6792c7e5c40c --- /dev/null +++ b/python/testData/resolve/ImportedTypingListInheritor/b.py @@ -0,0 +1,4 @@ +from typing import List + +class A(List[int]): + pass \ No newline at end of file diff --git a/python/testData/resolve/TypingListInheritor.py b/python/testData/resolve/TypingListInheritor.py new file mode 100644 index 000000000000..f9f6d9c957f1 --- /dev/null +++ b/python/testData/resolve/TypingListInheritor.py @@ -0,0 +1,5 @@ +from typing import List +class A(List[int]): + pass +A().append() +# \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleKwargsFields.py b/python/testData/stubs/ImportedTypingNamedTupleKwargsFields.py new file mode 100644 index 000000000000..1516a5f66782 --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleKwargsFields.py @@ -0,0 +1,4 @@ +from typing import NamedTuple +from b import fields + +nt = NamedTuple("name", **fields) \ No newline at end of file diff --git a/python/testData/typeshed/stdlib/2/collections_test.py b/python/testData/typeshed/stdlib/2/collections_test.py new file mode 100644 index 000000000000..b71032c3ff1a --- /dev/null +++ b/python/testData/typeshed/stdlib/2/collections_test.py @@ -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] \ No newline at end of file diff --git a/python/testData/typeshed/stdlib/2and3/collections_test.py b/python/testData/typeshed/stdlib/2and3/collections_test.py deleted file mode 100644 index 6ef1fd533607..000000000000 --- a/python/testData/typeshed/stdlib/2and3/collections_test.py +++ /dev/null @@ -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 diff --git a/python/testData/typeshed/stdlib/3/collections_test.py b/python/testData/typeshed/stdlib/3/collections_test.py new file mode 100644 index 000000000000..0dfb516fd4ce --- /dev/null +++ b/python/testData/typeshed/stdlib/3/collections_test.py @@ -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] \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 5f037d590cb8..f5381b40dbe6 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -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"); + } } diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index 5e1f216b7d75..a7c799671412 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -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 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, diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index ee439128e505..36d02b6b221d 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -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");