From aab0d3e87a05f670825331d275e2779f97599d89 Mon Sep 17 00:00:00 2001 From: fitermay Date: Wed, 12 Oct 2016 22:26:50 +0300 Subject: [PATCH] PY-19826 Fixed: Generic type of list not inferred when created from tuple Inherit PyTupleType from PyCollectionType so that getElementTypes().get(i) returns type of 'i'-th element in the tuple --- .../documentation/PyTypeModelBuilder.java | 30 ++++----- .../psi/impl/PyCallExpressionHelper.java | 33 ++++++---- .../python/psi/impl/PyFunctionImpl.java | 38 ++++++----- .../psi/types/PyCollectionTypeImpl.java | 17 ++--- .../python/psi/types/PyTupleType.java | 15 +++-- .../python/psi/types/PyTypeChecker.java | 63 ++++++++++++------- .../testData/quickdoc/HeterogeneousTuple.html | 1 + .../testData/quickdoc/HeterogeneousTuple.py | 4 ++ .../testData/quickdoc/HomogeneousTuple.html | 1 + python/testData/quickdoc/HomogeneousTuple.py | 4 ++ python/testData/quickdoc/UnknownTuple.html | 1 + python/testData/quickdoc/UnknownTuple.py | 4 ++ .../com/jetbrains/python/Py3TypeTest.java | 34 ++++++++++ .../com/jetbrains/python/PyQuickDocTest.java | 12 ++++ .../com/jetbrains/python/PyTypeTest.java | 16 +++++ 15 files changed, 191 insertions(+), 82 deletions(-) create mode 100644 python/testData/quickdoc/HeterogeneousTuple.html create mode 100644 python/testData/quickdoc/HeterogeneousTuple.py create mode 100644 python/testData/quickdoc/HomogeneousTuple.html create mode 100644 python/testData/quickdoc/HomogeneousTuple.py create mode 100644 python/testData/quickdoc/UnknownTuple.html create mode 100644 python/testData/quickdoc/UnknownTuple.py diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 4a0ad4dc587c..6c2ba27fdff6 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -18,6 +18,7 @@ package com.jetbrains.python.documentation; import com.google.common.collect.Collections2; import com.google.common.collect.Maps; import com.intellij.psi.PsiElement; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyTypingTypeProvider; import com.jetbrains.python.psi.types.*; @@ -197,7 +198,17 @@ public class PyTypeModelBuilder { myVisited.put(type, null); //mark as evaluating TypeModel result = null; - if (type instanceof PyCollectionType) { + if (type instanceof PyTupleType) { + final PyTupleType tupleType = (PyTupleType)type; + + final List elementTypes = tupleType.isHomogeneous() + ? Collections.singletonList(tupleType.getElementType(0)) + : tupleType.getElementTypes(myContext); + + final List elementModels = ContainerUtil.map(elementTypes, elementType -> build(elementType, true)); + result = new TupleType(elementModels, tupleType.isHomogeneous()); + } + else if (type instanceof PyCollectionType) { final String name = type.getName(); final List elementTypes = ((PyCollectionType)type).getElementTypes(myContext); boolean nullOnlyTypes = true; @@ -232,15 +243,6 @@ public class PyTypeModelBuilder { else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { result = build((PyCallableType)type); } - else if (type instanceof PyTupleType) { - final List elementModels = new ArrayList<>(); - final PyTupleType tupleType = (PyTupleType)type; - for (int i = 0; i < (tupleType.isHomogeneous() ? 1 : tupleType.getElementCount()); i++) { - final PyType elementType = tupleType.getElementType(i); - elementModels.add(build(elementType, true)); - } - result = new TupleType(elementModels, tupleType.isHomogeneous()); - } if (result == null) { result = type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); } @@ -364,7 +366,7 @@ public class PyTypeModelBuilder { @Override public void oneOf(OneOf oneOf) { myDepth++; - if (myDepth>MAX_DEPTH) { + if (myDepth > MAX_DEPTH) { add("..."); return; } @@ -393,7 +395,7 @@ public class PyTypeModelBuilder { @Override public void collectionOf(CollectionOf collectionOf) { myDepth++; - if (myDepth>MAX_DEPTH) { + if (myDepth > MAX_DEPTH) { add("..."); return; } @@ -416,7 +418,7 @@ public class PyTypeModelBuilder { @Override public void function(FunctionType function) { myDepth++; - if (myDepth>MAX_DEPTH) { + if (myDepth > MAX_DEPTH) { add("..."); return; } @@ -436,7 +438,7 @@ public class PyTypeModelBuilder { @Override public void param(ParamType param) { myDepth++; - if (myDepth>MAX_DEPTH) { + if (myDepth > MAX_DEPTH) { add("..."); return; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 37b423526564..a1048d61cf2e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -205,7 +205,7 @@ public class PyCallExpressionHelper { } } final List resolvedQualifiers = resolveResult != null ? resolveResult.getQualifiers() : null; - final List qualifiers = resolvedQualifiers != null ? resolvedQualifiers : Collections.emptyList(); + final List qualifiers = resolvedQualifiers != null ? resolvedQualifiers : Collections.emptyList(); final TypeEvalContext context = resolveContext.getTypeEvalContext(); if (resolved instanceof PyFunction) { final PyFunction function = (PyFunction)resolved; @@ -267,7 +267,7 @@ public class PyCallExpressionHelper { QualifiedResolveResult followed = callReference.followAssignmentsChain(resolveContext); final List qualifiers = followed.getQualifiers(); final PyExpression firstQualifier = qualifiers != null && !qualifiers.isEmpty() ? qualifiers.get(0) : null; - boolean isByInstance = isQualifiedByInstance(function, qualifiers != null ? qualifiers : Collections.emptyList(), + boolean isByInstance = isQualifiedByInstance(function, qualifiers != null ? qualifiers : Collections.emptyList(), resolveContext.getTypeEvalContext()); final boolean isConstructorCall = isConstructorName(function.getName()) && (!callReference.isQualified() || !isConstructorName(callReference.getName())); @@ -539,14 +539,21 @@ public class PyCallExpressionHelper { } if (init != null) { final PyType t = init.getCallType(context, call); - if (cls != null) { - if (init.getContainingClass() != cls) { - if (t instanceof PyCollectionType) { - final List elementTypes = ((PyCollectionType)t).getElementTypes(context); - return Ref.create(new PyCollectionTypeImpl(cls, false, elementTypes)); - } - return Ref.create(new PyClassTypeImpl(cls, false)); + if (cls != null && cls != init.getContainingClass()) { + if (t instanceof PyTupleType) { + final PyTupleType tupleType = (PyTupleType)t; + final List elementTypes = tupleType.getElementTypes(context); + final PyTupleType newTupleType = new PyTupleType(cls, elementTypes.toArray(new PyType[0]), tupleType.isHomogeneous()); + + return Ref.create(newTupleType); } + + if (t instanceof PyCollectionType) { + final List elementTypes = ((PyCollectionType)t).getElementTypes(context); + return Ref.create(new PyCollectionTypeImpl(cls, false, elementTypes)); + } + + return Ref.create(new PyClassTypeImpl(cls, false)); } if (t != null && !(t instanceof PyNoneType)) { return Ref.create(t); @@ -680,10 +687,10 @@ public class PyCallExpressionHelper { final PyCallExpression.PyMarkedCallee markedCallee = callExpression.resolveCallee(resolveContext, implicitOffset); if (markedCallee == null || argumentList == null) { - return new PyCallExpression.PyArgumentsMapping(callExpression, null, Collections.emptyMap(), - Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), Collections.emptyList(), - Collections.emptyMap()); + return new PyCallExpression.PyArgumentsMapping(callExpression, null, Collections.emptyMap(), + Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), + Collections.emptyMap()); } final TypeEvalContext context = resolveContext.getTypeEvalContext(); final List parameters = PyUtil.getParameters(markedCallee.getCallable(), context); diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index 23a49dc5cd54..3697fdb32ead 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -124,21 +124,21 @@ public class PyFunctionImpl extends PyBaseElementImpl implements public Icon getIcon(int flags) { PyPsiUtils.assertValid(this); final Property property = getProperty(); - if (property != null) { - if (property.getGetter().valueOrNull() == this) { - return PythonIcons.Python.PropertyGetter; - } - if (property.getSetter().valueOrNull() == this) { - return PythonIcons.Python.PropertySetter; - } - if (property.getDeleter().valueOrNull() == this) { - return PythonIcons.Python.PropertyDeleter; - } - return PlatformIcons.PROPERTY_ICON; + if (property != null) { + if (property.getGetter().valueOrNull() == this) { + return PythonIcons.Python.PropertyGetter; } - if (getContainingClass() != null) { - return PlatformIcons.METHOD_ICON; + if (property.getSetter().valueOrNull() == this) { + return PythonIcons.Python.PropertySetter; } + if (property.getDeleter().valueOrNull() == this) { + return PythonIcons.Python.PropertyDeleter; + } + return PlatformIcons.PROPERTY_ICON; + } + if (getContainingClass() != null) { + return PlatformIcons.METHOD_ICON; + } return PythonIcons.Python.Function; } @@ -324,14 +324,18 @@ public class PyFunctionImpl extends PyBaseElementImpl implements public void visitPyYieldExpression(PyYieldExpression node) { final PyExpression expr = node.getExpression(); final PyType type = expr != null ? context.getType(expr) : null; + if (node.isDelegating()) { - if (type instanceof PyCollectionType) { + if (type instanceof PyTupleType) { + types.addAll(((PyTupleType)type).getElementTypes(context)); + } + else if (type instanceof PyCollectionType) { final PyCollectionType collectionType = (PyCollectionType)type; // TODO: Select the parameter types that matches T in Iterable[T] final List elementTypes = collectionType.getElementTypes(context); types.add(elementTypes.isEmpty() ? null : elementTypes.get(0)); } - else if (ArrayUtil.contains(type, cache.getListType(), cache.getDictType(), cache.getSetType())) { + else if (ArrayUtil.contains(type, cache.getListType(), cache.getDictType(), cache.getSetType(), cache.getTupleType())) { types.add(null); } else { @@ -605,7 +609,7 @@ public class PyFunctionImpl extends PyBaseElementImpl implements if (inlineComment != null && PyTypingTypeProvider.getTypeCommentValue(inlineComment.getText()) != null) { return inlineComment; } - + final PyStatementList statements = getStatementList(); if (statements.getStatements().length != 0) { final PsiComment comment = as(statements.getFirstChild(), PsiComment.class); @@ -621,7 +625,7 @@ public class PyFunctionImpl extends PyBaseElementImpl implements public String getTypeCommentAnnotation() { final PyFunctionStub stub = getStub(); if (stub != null) { - return stub.getTypeComment(); + return stub.getTypeComment(); } final PsiComment comment = getTypeComment(); if (comment != null) { diff --git a/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java index a729a1644b8d..e5f2689f7ee2 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCollectionTypeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,20 +77,13 @@ public class PyCollectionTypeImpl extends PyClassTypeImpl implements PyCollectio @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof PyCollectionType)) return false; + if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; - PyCollectionType type = (PyCollectionType)o; + final PyCollectionTypeImpl that = (PyCollectionTypeImpl)o; + + if (!myElementTypes.equals(that.myElementTypes)) return false; - final TypeEvalContext context = TypeEvalContext.codeInsightFallback(myClass.getProject()); - final List otherElementTypes = type.getElementTypes(context); - if (myElementTypes.size() != otherElementTypes.size()) return false; - for (int i = 0; i < myElementTypes.size(); i++) { - final PyType elementType = myElementTypes.get(i); - final PyType otherElementType = otherElementTypes.get(i); - if (elementType == null && otherElementType != null) return false; - if (elementType != null && !elementType.equals(otherElementType)) return false; - } return true; } diff --git a/python/src/com/jetbrains/python/psi/types/PyTupleType.java b/python/src/com/jetbrains/python/psi/types/PyTupleType.java index d3ed423c5757..822b94e6592b 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTupleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyTupleType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,11 +26,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.List; /** * @author yole */ -public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType { +public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType, PyCollectionType { private final PyType[] myElementTypes; private final boolean myHomogeneous; @@ -47,12 +48,12 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType public static PyTupleType createHomogeneous(@NotNull PsiElement anchor, @Nullable PyType elementType) { PyClass tuple = PyBuiltinCache.getInstance(anchor).getClass(PyNames.TUPLE); if (tuple != null) { - return new PyTupleType(tuple, new PyType[] {elementType}, true); + return new PyTupleType(tuple, new PyType[]{elementType}, true); } return null; } - PyTupleType(@NotNull PyClass tupleClass, @NotNull PyType[] elementTypes, boolean homogeneous) { + public PyTupleType(@NotNull PyClass tupleClass, @NotNull PyType[] elementTypes, boolean homogeneous) { super(tupleClass, false); myElementTypes = elementTypes; myHomogeneous = homogeneous; @@ -124,4 +125,10 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType result = 31 * result + (myElementTypes != null ? Arrays.hashCode(myElementTypes) : 0); return result; } + + @NotNull + @Override + public List getElementTypes(@NotNull TypeEvalContext context) { + return Arrays.asList(myElementTypes); + } } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 896038fa6d1b..c285a94db599 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -18,6 +18,7 @@ package com.jetbrains.python.psi.types; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; @@ -116,22 +117,7 @@ public class PyTypeChecker { if (expected instanceof PyClassType && actual instanceof PyClassType) { final PyClass superClass = ((PyClassType)expected).getPyClass(); final PyClass subClass = ((PyClassType)actual).getPyClass(); - if (expected instanceof PyCollectionType && actual instanceof PyCollectionType) { - if (!matchClasses(superClass, subClass, context)) { - return false; - } - // TODO: Match generic parameters based on the correspondence between the generic parameters of subClass and its base classes - final List superElementTypes = ((PyCollectionType)expected).getElementTypes(context); - final List subElementTypes = ((PyCollectionType)actual).getElementTypes(context); - for (int i = 0; i < subElementTypes.size(); i++) { - final PyType superElementType = i < superElementTypes.size() ? superElementTypes.get(i) : null; - if (!match(superElementType, subElementTypes.get(i), context, substitutions, recursive)) { - return false; - } - } - return true; - } - else if (expected instanceof PyTupleType && actual instanceof PyTupleType) { + if (expected instanceof PyTupleType && actual instanceof PyTupleType) { final PyTupleType superTupleType = (PyTupleType)expected; final PyTupleType subTupleType = (PyTupleType)actual; if (!superTupleType.isHomogeneous() && !subTupleType.isHomogeneous()) { @@ -163,6 +149,39 @@ public class PyTypeChecker { return match(superTupleType.getElementType(0), subTupleType.getElementType(0), context); } } + else if (expected instanceof PyCollectionType && actual instanceof PyTupleType) { + if (!matchClasses(superClass, subClass, context)) { + return false; + } + + final PyTupleType actualTupleType = (PyTupleType)actual; + final PyType superElementType = ContainerUtil.getFirstItem(((PyCollectionType)expected).getElementTypes(context)); + final PyType subElementType = actualTupleType.isHomogeneous() + ? actualTupleType.getElementType(0) + : PyUnionType.union(actualTupleType.getElementTypes(context)); + + if (!match(superElementType, subElementType, context, substitutions, recursive)) { + return false; + } + + return true; + } + else if (expected instanceof PyCollectionType && actual instanceof PyCollectionType) { + if (!matchClasses(superClass, subClass, context)) { + return false; + } + // TODO: Match generic parameters based on the correspondence between the generic parameters of subClass and its base classes + final List superElementTypes = ((PyCollectionType)expected).getElementTypes(context); + final List subElementTypes = ((PyCollectionType)actual).getElementTypes(context); + for (int i = 0; i < subElementTypes.size(); i++) { + final PyType superElementType = i < superElementTypes.size() ? superElementTypes.get(i) : null; + if (!match(superElementType, subElementTypes.get(i), context, substitutions, recursive)) { + return false; + } + } + return true; + } + else if (matchClasses(superClass, subClass, context)) { return true; } @@ -295,12 +314,6 @@ public class PyTypeChecker { collectGenerics(t, context, collected, visited); } } - else if (type instanceof PyCollectionType) { - final PyCollectionType collection = (PyCollectionType)type; - for (PyType elementType : collection.getElementTypes(context)) { - collectGenerics(elementType, context, collected, visited); - } - } else if (type instanceof PyTupleType) { final PyTupleType tuple = (PyTupleType)type; final int n = tuple.isHomogeneous() ? 1 : tuple.getElementCount(); @@ -308,6 +321,12 @@ public class PyTypeChecker { collectGenerics(tuple.getElementType(i), context, collected, visited); } } + else if (type instanceof PyCollectionType) { + final PyCollectionType collection = (PyCollectionType)type; + for (PyType elementType : collection.getElementTypes(context)) { + collectGenerics(elementType, context, collected, visited); + } + } else if (type instanceof PyCallableType) { final PyCallableType callable = (PyCallableType)type; final List parameters = callable.getParameters(context); diff --git a/python/testData/quickdoc/HeterogeneousTuple.html b/python/testData/quickdoc/HeterogeneousTuple.html new file mode 100644 index 000000000000..e13958e7cf6a --- /dev/null +++ b/python/testData/quickdoc/HeterogeneousTuple.html @@ -0,0 +1 @@ +def get_tuple()
Inferred type: () -> Tuple[int, int, str]
\ No newline at end of file diff --git a/python/testData/quickdoc/HeterogeneousTuple.py b/python/testData/quickdoc/HeterogeneousTuple.py new file mode 100644 index 000000000000..e694b2a7f6d3 --- /dev/null +++ b/python/testData/quickdoc/HeterogeneousTuple.py @@ -0,0 +1,4 @@ +def get_tuple() -> tuple[int, int, str]: + pass + +get_tuple() \ No newline at end of file diff --git a/python/testData/quickdoc/HomogeneousTuple.html b/python/testData/quickdoc/HomogeneousTuple.html new file mode 100644 index 000000000000..e50ee76fb6f2 --- /dev/null +++ b/python/testData/quickdoc/HomogeneousTuple.html @@ -0,0 +1 @@ +def get_tuple()
Inferred type: () -> Tuple[str, ...]
\ No newline at end of file diff --git a/python/testData/quickdoc/HomogeneousTuple.py b/python/testData/quickdoc/HomogeneousTuple.py new file mode 100644 index 000000000000..c6ce174c65e9 --- /dev/null +++ b/python/testData/quickdoc/HomogeneousTuple.py @@ -0,0 +1,4 @@ +def get_tuple() -> tuple[str, ...]: + pass + +get_tuple() \ No newline at end of file diff --git a/python/testData/quickdoc/UnknownTuple.html b/python/testData/quickdoc/UnknownTuple.html new file mode 100644 index 000000000000..ede32281e009 --- /dev/null +++ b/python/testData/quickdoc/UnknownTuple.html @@ -0,0 +1 @@ +def get_tuple()
Inferred type: () -> tuple
\ No newline at end of file diff --git a/python/testData/quickdoc/UnknownTuple.py b/python/testData/quickdoc/UnknownTuple.py new file mode 100644 index 000000000000..863f7b55996b --- /dev/null +++ b/python/testData/quickdoc/UnknownTuple.py @@ -0,0 +1,4 @@ +def get_tuple() -> tuple: + pass + +get_tuple() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index 232d6df67327..d96d26f4dc7f 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -84,6 +84,40 @@ public class Py3TypeTest extends PyTestCase { " pass")); } + public void testYieldFromHomogeneousTuple() { + myFixture.copyDirectoryToProject("typing", ""); + doTest("str", + "import typing\n"+ + "def get_tuple() -> typing.Tuple[str, ...]:\n" + + " pass\n" + + "def gen()\n" + + " yield from get_tuple()\n" + + "for expr in gen():" + + " pass"); + } + + public void testYieldFromHeterogeneousTuple() { + myFixture.copyDirectoryToProject("typing", ""); + doTest("Union[int, str]", + "import typing\n" + + "def get_tuple() -> typing.Tuple[int, int, str]:\n" + + " pass\n" + + "def gen()\n" + + " yield from get_tuple()\n" + + "for expr in gen():" + + " pass"); + } + + public void testYieldFromUnknownTuple() { + doTest("Any", + "def get_tuple() -> tuple:\n" + + " pass\n" + + "def gen()\n" + + " yield from get_tuple()\n" + + "for expr in gen():" + + " pass"); + } + public void testYieldFromUnknownList() { doTest("Any", "def get_list() -> list:\n" + diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index 45d69d2d660d..c59420e0c17a 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -265,4 +265,16 @@ public class PyQuickDocTest extends LightMarkedTestCase { public void testOptionalParameterType() { checkHTMLOnly(); } + + public void testHomogeneousTuple() { + runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); + } + + public void testHeterogeneousTuple() { + runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); + } + + public void testUnknownTuple() { + runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly); + } } diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 6148279f66f7..3cf1496df768 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -886,6 +886,22 @@ public class PyTypeTest extends PyTestCase { " expr, foo = xs\n"); } + // PY-19826 + public void testListFromTuple() { + doTest("List[Union[str, int]]", + "expr = list(('1', 2, 3))"); + } + + public void testDictFromTuple() { + doTest("Dict[Union[str, int], Union[str, int]]", + "expr = dict((('1', 1), (2, 2), (3, '3')))"); + } + + public void testSetFromTuple() { + doTest("Set[Union[str, int]]", + "expr = set(('1', 2, 3))"); + } + public void testHomogeneousTupleSubstitution() { runWithLanguageLevel( LanguageLevel.PYTHON35,