PY-44974 PEP 604: Support Python3.10 unions

Support | syntax for python unions.
Also support type | None as a new style for Optional.
Fix highlighting unresolved reference issues with | syntax.
Type inference from isinstance and issubclass.
Add quick fix to switch to old-style syntax in earlier python versions (<3.10)
Fix quick documentation to render new syntax.

(cherry picked from commit 6a64ee12c2d8503a0ef102e8b67cb0b95ce77999)

IJ-MR-8400

GitOrigin-RevId: c26b868fc61f26936a3316ec06f78b66d75f6857
This commit is contained in:
andrey.matveev
2021-03-31 10:51:36 +07:00
committed by intellij-monorepo-bot
parent 51f447141a
commit ebe8f93812
49 changed files with 854 additions and 122 deletions

View File

@@ -448,7 +448,7 @@ QFIX.coroutine.is.not.awaited=Coroutine is not awaited
# Actions and associated commands
ACT.CMD.use.import=Use an imported module
ACT.qualify.with.module=Qualify with an imported module
ACT.from.some.module.import=Import from
ACT.from.some.module.import=Import from \u2026
filetype.python.docstring.description=Python docstring
@@ -486,7 +486,7 @@ INSP.package.requirements.administrator.privileges.required.description=\
INSP.package.requirements.administrator.privileges.required.button.configure=Configure
INSP.package.requirements.administrator.privileges.required.button.install.anyway=Install Anyway
INSP.package.requirements.requirements.file.empty=Requirements file is empty
QFIX.add.imported.packages.to.requirements=Add imported packages to requirements
QFIX.add.imported.packages.to.requirements=Add imported packages to requirements\u2026
INSP.pep8.ignore.base.class=Ignore Base Class
INSP.pep8.ignore.method.names.for.descendants.of.class=Ignore method names for descendants of class
@@ -730,6 +730,7 @@ INSP.compatibility.feature.have.module=have module {0}
INSP.compatibility.feature.allow.async.and.await.as.names=allow 'async' and 'await' as names
INSP.compatibility.old.dict.methods.not.available.in.py3=dict.iterkeys(), dict.iteritems(), and dict.itervalues() methods are not available in Python 3
INSP.compatibility.basestring.type.not.available.in.py3=basestring type is not available in Python 3
INSP.compatibility.new.union.syntax.not.available.in.earlier.version=allow writing union types as X | Y
# PyUnnecessaryBackslashInspection
INSP.NAME.unnecessary.backslash=Unnecessary backslash
@@ -1050,7 +1051,7 @@ INSP.type.hints.type.variables.must.not.be.redefined=Type variables must not be
INSP.type.hints.typevar.expects.string.literal.as.first.argument='TypeVar()' expects a string literal as first argument
INSP.type.hints.argument.to.typevar.must.be.string.equal.to.variable.name=The argument to 'TypeVar()' must be a string equal to the variable name to which it is assigned
INSP.type.hints.bivariant.type.variables.are.not.supported=Bivariant type variables are not supported
INSP.type.hints.typevar.constraints.cannot.be.combined.with.bound=Constraints cannot be combined with bound=
INSP.type.hints.typevar.constraints.cannot.be.combined.with.bound=Constraints cannot be combined with bound=\u2026
INSP.type.hints.single.typevar.constraint.not.allowed=A single constraint is not allowed
INSP.type.hints.typevar.constraints.cannot.be.parametrized.by.type.variables=Constraints cannot be parametrized by type variables
INSP.type.hints.type.variables.cannot.be.used.with.instance.class.checks=Type variables cannot be used with instance and class checks
@@ -1084,6 +1085,7 @@ QFIX.replace.with.typing.alias=Replace with typing alias
QFIX.remove.type.comment=Remove the type comment
QFIX.remove.annotation=Remove the annotation
QFIX.replace.with.type.name=Replace with the type name
QFIX.replace.with.old.union.style=Replace with an old-style Union
# PyInspectionsSuppressor
INSP.python.suppressor.suppress.for.function=Suppress for a function

View File

@@ -9,6 +9,7 @@ import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyEvaluator;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -53,7 +54,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
final PyReferenceExpression target = (PyReferenceExpression)args[0];
final PyExpression typeElement = args[1];
pushAssertion(target, myPositive, false, context -> context.getType(typeElement));
pushAssertion(target, myPositive, false, context -> context.getType(typeElement), typeElement);
}
}
else if (node.isCalleeText(PyNames.CALLABLE_BUILTIN)) {
@@ -61,7 +62,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 -> PyTypingTypeProvider.createTypingCallableType(node));
pushAssertion(target, myPositive, false, context -> PyTypingTypeProvider.createTypingCallableType(node), null);
}
}
else if (node.isCalleeText(PyNames.ISSUBCLASS)) {
@@ -70,7 +71,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
final PyReferenceExpression target = (PyReferenceExpression)args[0];
final PyExpression typeElement = args[1];
pushAssertion(target, myPositive, true, context -> context.getType(typeElement));
pushAssertion(target, myPositive, true, context -> context.getType(typeElement), typeElement);
}
}
}
@@ -80,7 +81,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
if (myPositive && (isIfReferenceStatement(node) || isIfReferenceConditionalStatement(node) || isIfNotReferenceStatement(node))) {
// we could not suggest `None` because it could be a reference to an empty collection
// so we could push only non-`None` assertions
pushAssertion(node, !myPositive, false, context -> PyNoneType.INSTANCE);
pushAssertion(node, !myPositive, false, context -> PyNoneType.INSTANCE, null);
return;
}
@@ -102,12 +103,12 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
final PyReferenceExpression target = (PyReferenceExpression)(rightIsNone ? lhs : rhs);
if (node.isOperator(PyNames.IS)) {
pushAssertion(target, myPositive, false, context -> PyNoneType.INSTANCE);
pushAssertion(target, myPositive, false, context -> PyNoneType.INSTANCE, null);
return;
}
if (node.isOperator("isnot")) {
pushAssertion(target, !myPositive, false, context -> PyNoneType.INSTANCE);
pushAssertion(target, !myPositive, false, context -> PyNoneType.INSTANCE, null);
return;
}
}
@@ -136,8 +137,9 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
@Nullable PyType suggested,
boolean positive,
boolean transformToDefinition,
@NotNull TypeEvalContext context) {
final PyType transformedType = transformTypeFromAssertion(suggested, transformToDefinition);
@NotNull TypeEvalContext context,
@Nullable PyExpression typeElement) {
final PyType transformedType = transformTypeFromAssertion(suggested, transformToDefinition, context, typeElement);
if (positive) {
if (!(initial instanceof PyUnionType) &&
!(initial instanceof PyStructuralType) &&
@@ -159,18 +161,37 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
}
@Nullable
private static PyType transformTypeFromAssertion(@Nullable PyType type, boolean transformToDefinition) {
private static PyType transformTypeFromAssertion(@Nullable PyType type, boolean transformToDefinition, @NotNull TypeEvalContext context,
@Nullable PyExpression typeElement) {
if (type instanceof PyTupleType) {
final List<PyType> members = new ArrayList<>();
final PyTupleType tupleType = (PyTupleType)type;
final int count = tupleType.getElementCount();
for (int i = 0; i < count; i++) {
members.add(transformTypeFromAssertion(tupleType.getElementType(i), transformToDefinition));
final PyTupleExpression tupleExpression = PyUtil
.as(PyPsiUtils.flattenParens(PyUtil.as(typeElement, PyParenthesizedExpression.class)), PyTupleExpression.class);
if (tupleExpression != null && tupleExpression.getElements().length == count) {
final PyExpression[] elements = tupleExpression.getElements();
for (int i = 0; i < count; i++) {
members.add(transformTypeFromAssertion(tupleType.getElementType(i), transformToDefinition, context, elements[i]));
}
}
else {
for (int i = 0; i < count; i++) {
members.add(transformTypeFromAssertion(tupleType.getElementType(i), transformToDefinition, context, null));
}
}
return PyUnionType.union(members);
}
else if (type instanceof PyUnionType) {
return ((PyUnionType)type).map(member -> transformTypeFromAssertion(member, transformToDefinition));
return ((PyUnionType)type).map(member -> transformTypeFromAssertion(member, transformToDefinition, context, null));
}
else if (type instanceof PyClassType && "types.Union".equals(((PyClassType)type).getClassQName()) && typeElement != null) {
final Ref<PyType> typeFromTypingProvider = PyTypingTypeProvider.getType(typeElement, context);
if (typeFromTypingProvider != null) {
return transformTypeFromAssertion(typeFromTypingProvider.get(), transformToDefinition, context, null);
}
}
else if (type instanceof PyInstantiableType) {
final PyInstantiableType instantiableType = (PyInstantiableType)type;
@@ -182,11 +203,13 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
private void pushAssertion(@NotNull PyReferenceExpression target,
boolean positive,
boolean transformToDefinition,
@NotNull Function<TypeEvalContext, PyType> suggestedType) {
@NotNull Function<TypeEvalContext, PyType> suggestedType,
@Nullable PyExpression typeElement) {
final InstructionTypeCallback typeCallback = new InstructionTypeCallback() {
@Override
public Ref<PyType> getType(TypeEvalContext context, @Nullable PsiElement anchor) {
return createAssertionType(context.getType(target), suggestedType.apply(context), positive, transformToDefinition, context);
return createAssertionType(context.getType(target), suggestedType.apply(context), positive, transformToDefinition, context,
typeElement);
}
};

View File

@@ -274,11 +274,11 @@ public final class PyTypeHintGenerationUtil {
}
}
private static void addImportsForTypeAnnotations(@NotNull List<PyType> types,
@NotNull TypeEvalContext context,
@NotNull PsiFile file) {
final Set<PsiNamedElement> symbols = new HashSet<>();
final Set<String> namesFromTyping = new HashSet<>();
public static void addImportsForTypeAnnotations(@NotNull List<PyType> types,
@NotNull TypeEvalContext context,
@NotNull PsiFile file) {
final Set<PsiNamedElement> symbols = new LinkedHashSet<>();
final Set<String> namesFromTyping = new LinkedHashSet<>();
for (PyType type : types) {
collectImportTargetsFromType(type, context, symbols, namesFromTyping);
@@ -305,7 +305,9 @@ public final class PyTypeHintGenerationUtil {
else if (type instanceof PyUnionType) {
final Collection<PyType> members = ((PyUnionType)type).getMembers();
final boolean isOptional = members.size() == 2 && members.contains(PyNoneType.INSTANCE);
typingTypes.add(isOptional ? "Optional" : "Union");
if (!PyTypingTypeProvider.isBitwiseOrUnionAvailable(context)) {
typingTypes.add(isOptional ? "Optional" : "Union");
}
for (PyType pyType : members) {
collectImportTargetsFromType(pyType, context, symbols, typingTypes);
}

View File

@@ -19,6 +19,7 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyCustomType;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
@@ -735,6 +736,45 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
return union != null || foundAny ? Ref.create(union) : null;
}
@Nullable
private static Ref<PyType> getTypeFromBitwiseOrOperator(@NotNull PyBinaryExpression expression, @NotNull Context context) {
if (expression.getOperator() != PyTokenTypes.OR) return null;
PyExpression left = expression.getLeftExpression();
PyExpression right = expression.getRightExpression();
if (left != null && right != null) {
Ref<PyType> leftType = getType(left, context);
Ref<PyType> rightType = getType(right, context);
PyType union = null;
if (leftType != null && rightType != null) {
union = PyUnionType.union(leftType.get(), rightType.get());
}
else if (leftType != null) {
union = leftType.get();
}
else if (rightType != null) {
union = rightType.get();
}
return union != null ? Ref.create(union) : null;
}
return null;
}
public static boolean isBitwiseOrUnionAvailable(@NotNull TypeEvalContext context) {
final PsiFile originFile = context.getOrigin();
return originFile == null || isBitwiseOrUnionAvailable(originFile);
}
public static boolean isBitwiseOrUnionAvailable(@NotNull PsiElement element) {
if (LanguageLevel.forElement(element).isAtLeast(LanguageLevel.PYTHON310)) return true;
PsiFile file = element.getContainingFile();
if (file instanceof PyFile && ((PyFile)file).hasImportFromFuture(FutureFeature.ANNOTATIONS)) {
return file == element || PsiTreeUtil.getParentOfType(element, PyAnnotation.class, false, ScopeOwner.class) != null;
}
return false;
}
@Nullable
private static Ref<PyType> getTypeForResolvedElement(@Nullable PyTargetExpression alias,
@NotNull PsiElement resolved,
@@ -747,6 +787,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
context.getExpressionCache().add(alias);
}
try {
final Ref<PyType> typeFromParenthesizedExpression = getTypeFromParenthesizedExpression(resolved, context);
if (typeFromParenthesizedExpression != null) {
return typeFromParenthesizedExpression;
}
final PyType unionType = getUnionType(resolved, context);
if (unionType != null) {
return Ref.create(unionType);
@@ -816,6 +860,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
if (classType != null) {
return classType;
}
final Ref<PyType> unionTypeFromBinaryOr = getTypeFromBinaryExpression(resolved, context);
if (unionTypeFromBinaryOr != null) {
return unionTypeFromBinaryOr;
}
return null;
}
finally {
@@ -825,6 +873,23 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
}
}
@Nullable
private static Ref<PyType> getTypeFromBinaryExpression(@NotNull PsiElement resolved, @NotNull Context context) {
if (resolved instanceof PyBinaryExpression) {
return getTypeFromBitwiseOrOperator((PyBinaryExpression)resolved, context);
}
return null;
}
@Nullable
private static Ref<PyType> getTypeFromParenthesizedExpression(@NotNull PsiElement resolved, @NotNull Context context) {
if (resolved instanceof PyParenthesizedExpression) {
final PyExpression containedExpression = PyPsiUtils.flattenParens((PyExpression)resolved);
return containedExpression != null ? getType(containedExpression, context) : null;
}
return null;
}
@Nullable
private static Ref<PyType> getExplicitTypeAliasType(@NotNull PsiElement resolved) {
if (resolved instanceof PyQualifiedNameOwner) {

View File

@@ -6,6 +6,7 @@ import com.google.common.collect.Maps;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
@@ -63,9 +64,11 @@ public class PyTypeModelBuilder {
static final class OneOf extends TypeModel {
private final Collection<TypeModel> oneOfTypes;
private final boolean bitwiseOrUnionAllowed;
private OneOf(Collection<TypeModel> oneOfTypes) {
private OneOf(Collection<TypeModel> oneOfTypes, boolean bitwiseOrUnionAllowed) {
this.oneOfTypes = oneOfTypes;
this.bitwiseOrUnionAllowed = bitwiseOrUnionAllowed;
}
@Override
@@ -116,9 +119,11 @@ public class PyTypeModelBuilder {
static final class UnknownType extends TypeModel {
private final TypeModel type;
private final boolean bitwiseOrUnionAllowed;
private UnknownType(TypeModel type) {
private UnknownType(TypeModel type, boolean bitwiseOrUnionAllowed) {
this.type = type;
this.bitwiseOrUnionAllowed = bitwiseOrUnionAllowed;
}
@Override
@@ -129,9 +134,11 @@ public class PyTypeModelBuilder {
static final class OptionalType extends TypeModel {
private final TypeModel type;
private final boolean bitwiseOrUnionAllowed;
private OptionalType(TypeModel type) {
private OptionalType(TypeModel type, boolean bitwiseOrUnionAllowed) {
this.type = type;
this.bitwiseOrUnionAllowed = bitwiseOrUnionAllowed;
}
@Override
@@ -303,24 +310,26 @@ public class PyTypeModelBuilder {
if (!literalsAndOthers.second.isEmpty()) {
final List<TypeModel> otherTypeModels = ContainerUtil.map(literalsAndOthers.second, t -> build(t, false));
result = new OneOf(ContainerUtil.prepend(otherTypeModels, oneOfLiterals));
result = new OneOf(ContainerUtil.prepend(otherTypeModels, oneOfLiterals),
PyTypingTypeProvider.isBitwiseOrUnionAvailable(myContext));
}
else {
result = oneOfLiterals;
}
}
else if (optionalType != null) {
result = new OptionalType(build(optionalType.get(), true));
result = new OptionalType(build(optionalType.get(), true), PyTypingTypeProvider.isBitwiseOrUnionAvailable(myContext));
}
else if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type, false, myContext)) {
result = new UnknownType(build(unionType.excludeNull(), true));
result = new UnknownType(build(unionType.excludeNull(), true), PyTypingTypeProvider.isBitwiseOrUnionAvailable(myContext));
}
else if (ContainerUtil.all(unionMembers, t -> t instanceof PyClassType && ((PyClassType)t).isDefinition())) {
final List<TypeModel> instanceTypes = ContainerUtil.map(unionMembers, t -> build(((PyClassType)t).toInstance(), allowUnions));
result = new ClassObjectType(new OneOf(instanceTypes));
result = new ClassObjectType(new OneOf(instanceTypes, PyTypingTypeProvider.isBitwiseOrUnionAvailable(myContext)));
}
else {
result = new OneOf(Collections2.transform(unionMembers, t -> build(t, false)));
result = new OneOf(Collections2.transform(unionMembers, t -> build(t, false)),
PyTypingTypeProvider.isBitwiseOrUnionAvailable(myContext));
}
}
else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) {
@@ -427,10 +436,17 @@ public class PyTypeModelBuilder {
public void unknown(UnknownType type) {
final TypeModel nested = type.type;
if (nested != null) {
add("Union[");
nested.accept(this);
add(", " + PyNames.UNKNOWN_TYPE);
add("]");
if (type.bitwiseOrUnionAllowed) {
nested.accept(this);
add(" | ");
add(PyNames.UNKNOWN_TYPE);
}
else {
add("Union[");
nested.accept(this);
add(", " + PyNames.UNKNOWN_TYPE);
add("]");
}
}
}
}
@@ -528,17 +544,26 @@ public class PyTypeModelBuilder {
add("...");
return;
}
add("Union[");
processList(oneOf.oneOfTypes);
add("]");
if (oneOf.bitwiseOrUnionAllowed) {
processList(oneOf.oneOfTypes, " | ");
}
else {
add("Union[");
processList(oneOf.oneOfTypes);
add("]");
}
myDepth--;
}
protected void processList(@NotNull Collection<TypeModel> list) {
processList(list, ", ");
}
protected void processList(@NotNull Collection<TypeModel> list, @NotNull String separator) {
boolean first = true;
for (TypeModel t : list) {
if (!first) {
add(", ");
add(separator);
}
else {
first = false;
@@ -637,9 +662,15 @@ public class PyTypeModelBuilder {
@Override
public void optional(OptionalType type) {
add("Optional[");
type.type.accept(this);
add("]");
if (type.bitwiseOrUnionAllowed) {
type.type.accept(this);
add(" | None");
}
else {
add("Optional[");
type.type.accept(this);
add("]");
}
}
@Override

View File

@@ -13,6 +13,7 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.QualifiedName
import com.jetbrains.python.PyNames
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.PyTokenTypes
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner
@@ -22,6 +23,7 @@ import com.jetbrains.python.codeInsight.imports.AddImportHelper
import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority
import com.jetbrains.python.codeInsight.typeHints.PyTypeHintFile
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.isBitwiseOrUnionAvailable
import com.jetbrains.python.documentation.PythonDocumentationProvider
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyBuiltinCache
@@ -269,13 +271,26 @@ class PyTypeHintsInspection : PyInspection() {
private fun checkInstanceAndClassChecks(call: PyCallExpression) {
if (call.isCalleeText(PyNames.ISINSTANCE, PyNames.ISSUBCLASS)) {
val base = call.arguments.getOrNull(1) ?: return
checkInstanceAndClassChecksOnTypeVar(base)
checkInstanceAndClassChecksOnReference(base)
checkInstanceAndClassChecksOnSubscription(base)
checkInstanceAndClassChecksOn(base)
}
}
private fun checkInstanceAndClassChecksOn(base: PyExpression) {
if (base is PyBinaryExpression && base.operator == PyTokenTypes.OR) {
if (isBitwiseOrUnionAvailable(base)) {
val left = base.leftExpression
val right = base.rightExpression
if (left != null) checkInstanceAndClassChecksOn(left)
if (right != null) checkInstanceAndClassChecksOn(right)
}
return
}
checkInstanceAndClassChecksOnTypeVar(base)
checkInstanceAndClassChecksOnReference(base)
checkInstanceAndClassChecksOnSubscription(base)
}
private fun checkInstanceAndClassChecksOnTypeVar(base: PyExpression) {
val type = myTypeEvalContext.getType(base)
if (type is PyGenericType && !type.isDefinition ||
@@ -341,8 +356,6 @@ class PyTypeHintsInspection : PyInspection() {
when (qName) {
PyTypingTypeProvider.GENERIC,
PyTypingTypeProvider.UNION,
PyTypingTypeProvider.OPTIONAL,
PyTypingTypeProvider.CLASS_VAR,
PyTypingTypeProvider.FINAL,
PyTypingTypeProvider.FINAL_EXT,
@@ -350,12 +363,26 @@ class PyTypeHintsInspection : PyInspection() {
PyTypingTypeProvider.LITERAL_EXT,
PyTypingTypeProvider.ANNOTATED,
PyTypingTypeProvider.ANNOTATED_EXT -> {
val shortName = qName.substringAfterLast('.')
registerProblem(base, PyPsiBundle.message("INSP.type.hints.type.cannot.be.used.with.instance.class.checks", shortName),
ProblemHighlightType.GENERIC_ERROR)
registerParametrizedGenericsProblem(qName, base)
return@forEach
}
PyTypingTypeProvider.UNION,
PyTypingTypeProvider.OPTIONAL -> {
if (!isBitwiseOrUnionAvailable(base)) {
registerParametrizedGenericsProblem(qName, base)
}
else if (base is PySubscriptionExpression) {
val indexExpr = base.indexExpression
if (indexExpr is PyTupleExpression) {
indexExpr.elements.forEach { tupleElement -> checkInstanceAndClassChecksOn(tupleElement) }
}
else if (indexExpr != null) {
checkInstanceAndClassChecksOn(indexExpr)
}
}
}
PyTypingTypeProvider.CALLABLE,
PyTypingTypeProvider.TYPE,
PyTypingTypeProvider.PROTOCOL,
@@ -385,6 +412,12 @@ class PyTypeHintsInspection : PyInspection() {
}
}
private fun registerParametrizedGenericsProblem(qName: String, base: PsiElement) {
val shortName = qName.substringAfterLast('.')
registerProblem(base, PyPsiBundle.message("INSP.type.hints.type.cannot.be.used.with.instance.class.checks", shortName),
ProblemHighlightType.GENERIC_ERROR)
}
private fun checkParenthesesOnGenerics(call: PyCallExpression) {
val callee = call.callee
if (callee is PyReferenceExpression) {
@@ -413,8 +446,10 @@ class PyTypeHintsInspection : PyInspection() {
.asSequence()
.filterIsInstance<PyReferenceExpression>()
.filter { genericQName in PyResolveUtil.resolveImportedElementQNameLocally(it) }
.forEach { registerProblem(it, PyPsiBundle.message("INSP.type.hints.cannot.inherit.from.plain.generic"),
ProblemHighlightType.GENERIC_ERROR) }
.forEach {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.cannot.inherit.from.plain.generic"),
ProblemHighlightType.GENERIC_ERROR)
}
}
private fun checkGenericDuplication(superClassExpressions: List<PyExpression>) {
@@ -431,8 +466,10 @@ class PyTypeHintsInspection : PyInspection() {
.any { genericQName in PyResolveUtil.resolveImportedElementQNameLocally(it) }
}
.drop(1)
.forEach { registerProblem(it, PyPsiBundle.message("INSP.type.hints.cannot.inherit.from.generic.multiple.times"),
ProblemHighlightType.GENERIC_ERROR) }
.forEach {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.cannot.inherit.from.generic.multiple.times"),
ProblemHighlightType.GENERIC_ERROR)
}
}
private fun checkGenericCompleteness(cls: PyClass) {
@@ -546,7 +583,8 @@ class PyTypeHintsInspection : PyInspection() {
private fun reportParameterizedTypeAlias(index: PyExpression) {
// There is another warning in the type hint context
if (!PyTypingTypeProvider.isInsideTypeHint(index, myTypeEvalContext)) {
registerProblem(index, PyPsiBundle.message("INSP.type.hints.type.alias.cannot.be.parameterized"), ProblemHighlightType.GENERIC_ERROR)
registerProblem(index, PyPsiBundle.message("INSP.type.hints.type.alias.cannot.be.parameterized"),
ProblemHighlightType.GENERIC_ERROR)
}
}

View File

@@ -22,16 +22,15 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
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;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author vlan
@@ -130,9 +129,17 @@ public class PyOperatorReference extends PyReferenceImpl {
typeEvalContext.trace("Side text is %s, type is %s", object.getText(), type);
if (type != null) {
final List<? extends RatedResolveResult> res =
type instanceof PyClassLikeType && ((PyClassLikeType)type).isDefinition()
? resolveDefinitionMember((PyClassLikeType)type, object, name)
: type.resolveMember(name, object, AccessDirection.of(myElement), myContext);
PyTypeUtil
.toStream(type)
.nonNull()
.map(
it -> it instanceof PyClassLikeType && ((PyClassLikeType)it).isDefinition()
? resolveDefinitionMember((PyClassLikeType)it, object, name)
: it.resolveMember(name, object, AccessDirection.of(myElement), myContext)
)
.filter(Objects::nonNull)
.flatMap(List::stream)
.collect(Collectors.toList());
if (!ContainerUtil.isEmpty(res)) {
results.addAll(res);

View File

@@ -3,15 +3,16 @@ package com.jetbrains.python.validation;
import com.google.common.collect.Sets;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.util.InspectionMessage;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
@@ -21,10 +22,14 @@ import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.inspections.quickfix.*;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -170,6 +175,8 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
else if (node.isOperator("@")) {
checkMatrixMultiplicationOperator(node.getPsiOperator());
}
checkBitwiseOrUnionSyntax(node);
}
private void checkMatrixMultiplicationOperator(PsiElement node) {
@@ -719,4 +726,89 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
PyPsiBundle.message("INSP.compatibility.feature.support.arbitrary.expressions.as.decorator"), decorator);
}
}
private void checkBitwiseOrUnionSyntax(@NotNull PyBinaryExpression node) {
if (node.getOperator() != PyTokenTypes.OR) return;
final PsiFile file = node.getContainingFile();
if (file == null ||
file instanceof PyFile &&
((PyFile)file).hasImportFromFuture(FutureFeature.ANNOTATIONS) &&
PsiTreeUtil.getParentOfType(node, PyAnnotation.class, false, ScopeOwner.class) != null) {
return;
}
// Consider only full expression not parts to have only one registered problem
if (PsiTreeUtil.getParentOfType(node, PyBinaryExpression.class, true, PyStatement.class) != null) return;
final TypeEvalContext context = TypeEvalContext.codeAnalysis(node.getProject(), node.getContainingFile());
final Ref<PyType> refType = PyTypingTypeProvider.getType(node, context);
if (refType != null && refType.get() instanceof PyUnionType) {
registerForAllMatchingVersions(level -> level.isOlderThan(LanguageLevel.PYTHON310),
PyPsiBundle.message("INSP.compatibility.new.union.syntax.not.available.in.earlier.version"),
node,
new ReplaceWithOldStyleUnionQuickFix());
}
}
private static class ReplaceWithOldStyleUnionQuickFix implements LocalQuickFix {
@Override
public @NotNull String getFamilyName() {
return PyPsiBundle.message("QFIX.replace.with.old.union.style");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiFile file = descriptor.getPsiElement().getContainingFile();
if (file == null) return;
final PsiElement descriptorElement = descriptor.getPsiElement();
if (!(descriptorElement instanceof PyBinaryExpression)) return;
final PyBinaryExpression expression = (PyBinaryExpression)descriptorElement;
final List<String> types = collectUnionTypes(expression);
final LanguageLevel languageLevel = LanguageLevel.forElement(file);
final AddImportHelper.ImportPriority priority = languageLevel.isAtLeast(LanguageLevel.PYTHON35) ?
AddImportHelper.ImportPriority.BUILTIN :
AddImportHelper.ImportPriority.THIRD_PARTY;
final PyCallExpression call = PsiTreeUtil.getParentOfType(expression, PyCallExpression.class);
if (call != null && call.isCalleeText(PyNames.ISINSTANCE, PyNames.ISSUBCLASS)) {
replaceOldExpressionWith(project, languageLevel, expression, "(" + StringUtil.join(types, ", ") + ")");
}
else {
if (types.size() == 2 && types.contains(PyNames.NONE)) {
final String notNoneType = PyNames.NONE.equals(types.get(0)) ? types.get(1) : types.get(0);
AddImportHelper.addOrUpdateFromImportStatement(file, "typing", "Optional", null, priority, expression);
replaceOldExpressionWith(project, languageLevel, expression, "Optional[" + notNoneType + "]");
}
else {
AddImportHelper.addOrUpdateFromImportStatement(file, "typing", "Union", null, priority, expression);
replaceOldExpressionWith(project, languageLevel, expression, "Union[" + StringUtil.join(types, ", ") + "]");
}
}
}
private static void replaceOldExpressionWith(@NotNull Project project, @NotNull LanguageLevel languageLevel,
@NotNull PyBinaryExpression expression, @NotNull String text) {
final PyExpression newExpression = PyElementGenerator
.getInstance(project)
.createExpressionFromText(languageLevel, text);
expression.replace(newExpression);
}
private static @NotNull List<String> collectUnionTypes(@Nullable PyExpression expression) {
if (expression == null) return Collections.emptyList();
if (expression instanceof PyBinaryExpression) {
final List<String> leftTypes = collectUnionTypes(((PyBinaryExpression)expression).getLeftExpression());
final List<String> rightTypes = collectUnionTypes(((PyBinaryExpression)expression).getRightExpression());
return ContainerUtil.concat(leftTypes, rightTypes);
}
else if (expression instanceof PyParenthesizedExpression) {
return collectUnionTypes(PyPsiUtils.flattenParens(expression));
}
else {
return Collections.singletonList(expression.getText());
}
}
}
}

View File

@@ -0,0 +1,2 @@
def foo() -> <error descr="Python version 3.9 does not allow writing union types as X | Y">int | None | str</error>:
return 42

View File

@@ -0,0 +1,4 @@
class A:
pass
assert isinstance(A(), <error descr="Python version 3.9 does not allow writing union types as X | Y">int | str</error>)

View File

@@ -0,0 +1,48 @@
from typing import Union, Optional
def expect_old_union(u: Union[int, str]):
expect_new_union(u)
expect_new_union(42)
expect_new_union("42")
expect_new_union(<warning descr="Expected type 'int | str', got 'list[int]' instead">[42]</warning>)
def expect_new_union(u: int | str):
expect_old_union(u)
expect_old_union(42)
expect_old_union("42")
expect_old_union(<warning descr="Expected type 'int | str', got 'list[int]' instead">[42]</warning>)
def expect_old_optional(u: Optional[int]):
expect_new_optional_none_first(u)
expect_new_optional_none_first(42)
expect_new_optional_none_first(None)
expect_new_optional_none_first(<warning descr="Expected type 'int | None', got 'list[int]' instead">[42]</warning>)
expect_new_optional_none_last(u)
expect_new_optional_none_last(42)
expect_new_optional_none_last(None)
expect_new_optional_none_last(<warning descr="Expected type 'int | None', got 'list[int]' instead">[42]</warning>)
def expect_new_optional_none_first(u: None | int):
expect_old_optional(u)
expect_old_optional(42)
expect_old_optional(None)
expect_old_optional(<warning descr="Expected type 'int | None', got 'list[int]' instead">[42]</warning>)
expect_new_optional_none_last(u)
expect_new_optional_none_last(42)
expect_new_optional_none_last(None)
expect_new_optional_none_last(<warning descr="Expected type 'int | None', got 'list[int]' instead">[42]</warning>)
def expect_new_optional_none_last(u: int | None):
expect_old_optional(u)
expect_old_optional(42)
expect_old_optional(None)
expect_old_optional(<warning descr="Expected type 'int | None', got 'list[int]' instead">[42]</warning>)
expect_new_optional_none_first(u)
expect_new_optional_none_first(42)
expect_new_optional_none_first(None)
expect_new_optional_none_first(<warning descr="Expected type 'int | None', got 'list[int]' instead">[42]</warning>)

View File

@@ -31,6 +31,6 @@ def expects_myclass_descendant_or_none(x: Optional[Type[T2]]):
pass
expects_myclass_descendant_or_none(MyClass)
expects_myclass_descendant_or_none(<warning descr="Expected type 'Optional[Any]' (matched generic type 'Optional[Type[T2]]'), got 'MyClass' instead">MyClass()</warning>)
expects_myclass_descendant_or_none(<warning descr="Expected type 'Optional[Any]' (matched generic type 'Optional[Type[T2]]'), got 'Type[object]' instead">object</warning>)
expects_myclass_descendant_or_none(<warning descr="Expected type 'Optional[Any]' (matched generic type 'Optional[Type[T2]]'), got 'object' instead">object()</warning>)
expects_myclass_descendant_or_none(<warning descr="Expected type 'Any | None' (matched generic type 'Type[T2] | None'), got 'MyClass' instead">MyClass()</warning>)
expects_myclass_descendant_or_none(<warning descr="Expected type 'Any | None' (matched generic type 'Type[T2] | None'), got 'Type[object]' instead">object</warning>)
expects_myclass_descendant_or_none(<warning descr="Expected type 'Any | None' (matched generic type 'Type[T2] | None'), got 'object' instead">object()</warning>)

View File

@@ -8,8 +8,8 @@ def expects_myclass_or_str1(x: Type[Union[MyClass, str]]):
expects_myclass_or_str1(MyClass)
expects_myclass_or_str1(str)
expects_myclass_or_str1(<warning descr="Expected type 'Type[Union[MyClass, str]]', got 'Type[int]' instead">int</warning>)
expects_myclass_or_str1(<warning descr="Expected type 'Type[Union[MyClass, str]]', got 'int' instead">42</warning>)
expects_myclass_or_str1(<warning descr="Expected type 'Type[MyClass | str]', got 'Type[int]' instead">int</warning>)
expects_myclass_or_str1(<warning descr="Expected type 'Type[MyClass | str]', got 'int' instead">42</warning>)
def expects_myclass_or_str2(x: Union[Type[MyClass], Type[str]]):
@@ -17,5 +17,5 @@ def expects_myclass_or_str2(x: Union[Type[MyClass], Type[str]]):
expects_myclass_or_str2(MyClass)
expects_myclass_or_str2(str)
expects_myclass_or_str2(<warning descr="Expected type 'Type[Union[MyClass, str]]', got 'Type[int]' instead">int</warning>)
expects_myclass_or_str2(<warning descr="Expected type 'Type[Union[MyClass, str]]', got 'int' instead">42</warning>)
expects_myclass_or_str2(<warning descr="Expected type 'Type[MyClass | str]', got 'Type[int]' instead">int</warning>)
expects_myclass_or_str2(<warning descr="Expected type 'Type[MyClass | str]', got 'int' instead">42</warning>)

View File

@@ -18,7 +18,7 @@ def e() -> int:
def f() -> Optional[str]:
x = int(input())
if x > 0:
return <warning descr="Expected type 'Optional[str]', got 'int' instead">42</warning>
return <warning descr="Expected type 'str | None', got 'int' instead">42</warning>
elif x == 0:
return 'abc'
else:
@@ -36,7 +36,7 @@ def h(x) -> int:
def i() -> Union[int, str]:
pass
def j(x) -> <warning descr="Expected to return 'Union[int, str]', got no return">Union[int, str]</warning>:
def j(x) -> <warning descr="Expected to return 'int | str', got no return">Union[int, str]</warning>:
x = 42
def k() -> None:

View File

@@ -4,4 +4,4 @@ TypeVar("T", int, str, bound=int, covariant=True, contravariant=True)
TypeVar("T", int, str, bound='int', covariant=True, contravariant=True)
TypeVar("T", int, <warning descr="Expected type 'type', got 'str' instead">'str'</warning>, bound=int, covariant=True, contravariant=True)
TypeVar("T", <warning descr="Expected type 'type', got 'str' instead">'int'</warning>, <warning descr="Expected type 'type', got 'str' instead">'str'</warning>, bound=int, covariant=True, contravariant=True)
TypeVar("T", <warning descr="Expected type 'type', got 'int' instead">0</warning>, <warning descr="Expected type 'type', got 'int' instead">1</warning>, <warning descr="Expected type 'Union[None, type, str]', got 'int' instead">bound=2</warning>, <warning descr="Expected type 'bool', got 'int' instead">covariant=3</warning>, <warning descr="Expected type 'bool', got 'int' instead">contravariant=4</warning>)
TypeVar("T", <warning descr="Expected type 'type', got 'int' instead">0</warning>, <warning descr="Expected type 'type', got 'int' instead">1</warning>, <warning descr="Expected type 'None | type | str', got 'int' instead">bound=2</warning>, <warning descr="Expected type 'bool', got 'int' instead">covariant=3</warning>, <warning descr="Expected type 'bool', got 'int' instead">contravariant=4</warning>)

View File

@@ -8,4 +8,4 @@ def expects_int_subclass_or_none(x: Optional[T]):
pass
expects_int_subclass_or_none(<warning descr="Expected type 'Optional[Any]' (matched generic type 'Optional[T]'), got 'str' instead">'foo'</warning>)
expects_int_subclass_or_none(<warning descr="Expected type 'Any | None' (matched generic type 'T | None'), got 'str' instead">'foo'</warning>)

View File

@@ -27,16 +27,16 @@ class B:
b = B()
open(<warning descr="Expected type 'Union[str, bytes, PathLike[str], PathLike[bytes], int]', got 'B' instead">b</warning>)
open(<warning descr="Expected type 'str | bytes | PathLike[str] | PathLike[bytes] | int', got 'B' instead">b</warning>)
os.fspath(<warning descr="Unexpected type(s):(B)Possible type(s):(PathLike[Union[Union[str, bytes], Any]])(bytes)(str)">b</warning>)
os.fsencode(<warning descr="Expected type 'Union[str, bytes, PathLike[str], PathLike[bytes]]', got 'B' instead">b</warning>)
os.fsdecode(<warning descr="Expected type 'Union[str, bytes, PathLike[str], PathLike[bytes]]', got 'B' instead">b</warning>)
os.fspath(<warning descr="Unexpected type(s):(B)Possible type(s):(PathLike[str | bytes | Any])(bytes)(str)">b</warning>)
os.fsencode(<warning descr="Expected type 'str | bytes | PathLike[str] | PathLike[bytes]', got 'B' instead">b</warning>)
os.fsdecode(<warning descr="Expected type 'str | bytes | PathLike[str] | PathLike[bytes]', got 'B' instead">b</warning>)
Path(<warning descr="Expected type 'Union[str, PathLike[str]]', got 'B' instead">b</warning>)
PurePath(<warning descr="Expected type 'Union[str, PathLike[str]]', got 'B' instead">b</warning>)
Path(<warning descr="Expected type 'str | PathLike[str]', got 'B' instead">b</warning>)
PurePath(<warning descr="Expected type 'str | PathLike[str]', got 'B' instead">b</warning>)
os.path.abspath(<warning descr="Unexpected type(s):(B)Possible type(s):(AnyStr)(PathLike[Union[Union[str, bytes], Any]])">b</warning>)
os.path.abspath(<warning descr="Unexpected type(s):(B)Possible type(s):(AnyStr)(PathLike[str | bytes | Any])">b</warning>)
# pathlib.PurePath

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable
from typing import Callable, Any
def func(x):

View File

@@ -4,8 +4,8 @@ from m1 import f, g, C, stub_only
def test_overloaded_function(x):
g(<warning descr="Expected type 'dict', got 'int' instead">f(10)</warning>)
g(<warning descr="Expected type 'dict', got 'str' instead">f('foo')</warning>)
g(<warning descr="Expected type 'dict', got 'Union[str, int]' instead">f(<warning descr="Unexpected type(s):(dict[int, int])Possible type(s):(str)(int)">{1: 2}</warning>)</warning>)
g(<warning descr="Expected type 'dict', got 'Union[str, int]' instead">f(x)</warning>)
g(<warning descr="Expected type 'dict', got 'str | int' instead">f(<warning descr="Unexpected type(s):(dict[int, int])Possible type(s):(str)(int)">{1: 2}</warning>)</warning>)
g(<warning descr="Expected type 'dict', got 'str | int' instead">f(x)</warning>)
def test_overloaded_subscription_operator_parameters():
@@ -25,5 +25,5 @@ def test_overloaded_binary_operator_parameters():
def test_stub_only_function(x):
g(<warning descr="Expected type 'dict', got 'int' instead">stub_only(10)</warning>)
g(<warning descr="Expected type 'dict', got 'str' instead">stub_only('foo')</warning>)
g(<warning descr="Expected type 'dict', got 'Union[str, int]' instead">stub_only(x)</warning>)
g(<warning descr="Expected type 'dict', got 'Union[str, int]' instead">stub_only(<warning descr="Unexpected type(s):(dict[int, int])Possible type(s):(str)(int)">{1: 2}</warning>)</warning>)
g(<warning descr="Expected type 'dict', got 'str | int' instead">stub_only(x)</warning>)
g(<warning descr="Expected type 'dict', got 'str | int' instead">stub_only(<warning descr="Unexpected type(s):(dict[int, int])Possible type(s):(str)(int)">{1: 2}</warning>)</warning>)

View File

@@ -0,0 +1,4 @@
class A:
pass
assert isinstance(A(), <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | str</warning>)

View File

@@ -0,0 +1,4 @@
class A:
pass
assert isinstance(A(), (int, str))

View File

@@ -0,0 +1,4 @@
class A:
pass
assert issubclass(A, <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | str</warning>)

View File

@@ -0,0 +1,4 @@
class A:
pass
assert issubclass(A, <caret>(int, str))

View File

@@ -0,0 +1,2 @@
def foo() -> <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | str</warning>:
return 42

View File

@@ -0,0 +1,2 @@
def foo() -> <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | str | list[dict[int, str]] | set[list[int]] | dict[str, int]</warning>:
return 42

View File

@@ -0,0 +1,5 @@
from typing import Union
def foo() -> Union[int, str, list[dict[int, str]], set[list[int]], dict[str, int]]:
return 42

View File

@@ -0,0 +1,5 @@
from typing import Union
def foo() -> Union[int, str]:
return 42

View File

@@ -0,0 +1,2 @@
def foo() -> <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | None | str</warning>:
return 42

View File

@@ -0,0 +1,5 @@
from typing import Union
def foo() -> <caret>Union[int, None, str]:
return 42

View File

@@ -0,0 +1,2 @@
def foo() -> <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y">int<caret> | None</warning>:
return 42

View File

@@ -0,0 +1,5 @@
from typing import Optional
def foo() -> Opt<caret>ional[int]:
return 42

View File

@@ -0,0 +1,2 @@
def foo() -> <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | str | None</warning>:
return 42

View File

@@ -0,0 +1,5 @@
from typing import Union
def foo() -> <caret>Union[int, str, None]:
return 42

View File

@@ -0,0 +1,2 @@
def foo() -> <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y">None | int<caret></warning>:
return 42

View File

@@ -0,0 +1,5 @@
from typing import Optional
def foo() -> Optional[int]<caret>:
return 42

View File

@@ -0,0 +1,4 @@
class A:
pass
assert isinstance(A(), <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y">(int<caret> | str | (list[str] | bool | float)) | dict[str, int]</warning>)

View File

@@ -0,0 +1,4 @@
class A:
pass
assert isinstance(A(), (int<caret>, str, list[str], bool, float, dict[str, int]))

View File

@@ -17,7 +17,7 @@ public class Py3TypeTest extends PyTestCase {
// PY-6702
public void testYieldFromType() {
doTest("Union[str, int, float]",
doTest("str | int | float",
"def subgen():\n" +
" for i in [1, 2, 3]:\n" +
" yield i\n" +
@@ -75,7 +75,7 @@ public class Py3TypeTest extends PyTestCase {
}
public void testYieldFromHeterogeneousTuple() {
doTest("Union[int, str]",
doTest("int | str",
"import typing\n" +
"def get_tuple() -> typing.Tuple[int, int, str]:\n" +
" pass\n" +
@@ -440,7 +440,7 @@ public class Py3TypeTest extends PyTestCase {
// PY-20757
public void testMinElseNone() {
doTest("Optional[Any]",
doTest("Any | None",
"def get_value(v):\n" +
" if v:\n" +
" return min(v)\n" +
@@ -473,7 +473,7 @@ public class Py3TypeTest extends PyTestCase {
public void testNumpyResolveRaterDoesNotIncreaseRateForNotNdarrayRightOperatorFoundInStub() {
myFixture.copyDirectoryToProject(TEST_DIRECTORY + getTestName(false), "");
doTest("Union[D1, D2]",
doTest("D1 | D2",
"class D1(object):\n" +
" pass\n" +
"class D2(object):\n" +
@@ -540,7 +540,7 @@ public class Py3TypeTest extends PyTestCase {
// PY-22513
public void testGenericKwargs() {
doTest("dict[str, Union[int, str]]",
doTest("dict[str, int | str]",
"from typing import Any, Dict, TypeVar\n" +
"\n" +
"T = TypeVar('T')\n" +

View File

@@ -431,7 +431,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
// PY-22005
public void testWithSpecifiedType() {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
final String expectedInfo = "a1: str, a2: Optional[str] = None, a3: Union[str, int, None] = None, a4: int, *args: int, **kwargs: int";
final String expectedInfo = "a1: str, a2: str | None = None, a3: str | int | None = None, a4: int, *args: int, **kwargs: int";
feignCtrlP(offset).check(expectedInfo, new String[]{"a1: str, "});
}
@@ -887,7 +887,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testInitializingTypeVar() {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
feignCtrlP(offset).check("self: TypeVar, name: str, *constraints: type, bound: Union[None, type, str] = ..., " +
feignCtrlP(offset).check("self: TypeVar, name: str, *constraints: type, bound: None | type | str = ..., " +
"covariant: bool = ..., contravariant: bool = ...",
new String[]{"name: str, "},
new String[]{"self: TypeVar, "});

View File

@@ -3793,7 +3793,7 @@ public class PyTypeTest extends PyTestCase {
runWithLanguageLevel(
LanguageLevel.getLatest(),
() -> {
doTest("Optional[int]",
doTest("int | None",
"from typing import TypedDict\n" +
"class A(TypedDict, total=False):\n" +
" x: int\n" +
@@ -3823,7 +3823,7 @@ public class PyTypeTest extends PyTestCase {
runWithLanguageLevel(
LanguageLevel.getLatest(),
() -> {
doTest("Union[int, str]",
doTest("int | str",
"from typing import TypedDict\n" +
"class A(TypedDict, total=False):\n" +
" x: int\n" +

View File

@@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
@@ -69,7 +70,7 @@ public class PyTypingTest extends PyTestCase {
}
public void testUnionType() {
doTest("Union[int, str]",
doTest("int | str",
"from typing import Union\n" +
"\n" +
"def f(expr: Union[int, str]):\n" +
@@ -212,7 +213,7 @@ public class PyTypingTest extends PyTestCase {
}
public void testAnyStrForUnknown() {
doTest("Union[Union[str, bytes], Any]",
doTest("str | bytes | Any",
"from typing import AnyStr\n" +
"\n" +
"def foo(x: AnyStr) -> AnyStr:\n" +
@@ -248,7 +249,7 @@ public class PyTypingTest extends PyTestCase {
}
public void testOptionalType() {
doTest("Optional[int]",
doTest("int | None",
"from typing import Optional\n" +
"\n" +
"def foo(expr: Optional[int]):\n" +
@@ -257,7 +258,7 @@ public class PyTypingTest extends PyTestCase {
// PY-28032
public void testOptionalOfAny() {
doTest("Optional[Any]",
doTest("Any | None",
"from typing import Optional, Any\n" +
"\n" +
"x = None # type: Optional[Any]\n" +
@@ -265,13 +266,13 @@ public class PyTypingTest extends PyTestCase {
}
public void testOptionalFromDefaultNone() {
doTest("Optional[int]",
doTest("int | None",
"def foo(expr: int = None):\n" +
" pass\n");
}
public void testFlattenUnions() {
doTest("Union[int, str, list]",
doTest("int | str | list",
"from typing import Union\n" +
"\n" +
"def foo(expr: Union[int, Union[str, list]]):\n" +
@@ -518,7 +519,7 @@ public class PyTypingTest extends PyTestCase {
// PY-18427
public void testConditionalType() {
doTest("Union[int, str]",
doTest("int | str",
"if something:\n" +
" Type = int\n" +
"else:\n" +
@@ -692,7 +693,7 @@ public class PyTypingTest extends PyTestCase {
// PY-18386
public void testRecursiveType() {
doTest("Union[int, Any]",
doTest("int | Any",
"from typing import Union\n" +
"\n" +
"Type = Union[int, 'Type']\n" +
@@ -701,7 +702,7 @@ public class PyTypingTest extends PyTestCase {
// PY-18386
public void testRecursiveType2() {
doTest("dict[str, Union[Union[str, int, float], Any]]",
doTest("dict[str, str | int | float | Any]",
"from typing import Dict, Union\n" +
"\n" +
"JsonDict = Dict[str, Union[str, int, float, 'JsonDict']]\n" +
@@ -712,7 +713,7 @@ public class PyTypingTest extends PyTestCase {
// PY-18386
public void testRecursiveType3() {
doTest("Union[Union[str, int], Any]",
doTest("str | int | Any",
"from typing import Union\n" +
"\n" +
"Type1 = Union[str, 'Type2']\n" +
@@ -761,7 +762,7 @@ public class PyTypingTest extends PyTestCase {
// PY-19858
public void testGetListItemByUnknown() {
doTest("Union[list, list[list]]",
doTest("list | list[list]",
"from typing import List\n" +
"\n" +
"def foo(x: List[List]):\n" +
@@ -1052,7 +1053,7 @@ public class PyTypingTest extends PyTestCase {
// PY-20057
public void testUnionOfClassObjectTypes() {
doTest("Type[Union[int, str]]",
doTest("Type[int | str]",
"from typing import Type, Union\n" +
"\n" +
"def f(x: Type[Union[int, str]]):\n" +
@@ -1251,7 +1252,7 @@ public class PyTypingTest extends PyTestCase {
// PY-24990
public void testSelfAnnotationReceiverUnionType() {
doTest("Union[A, B]",
doTest("A | B",
"from typing import TypeVar\n" +
"\n" +
"T = TypeVar('T')\n" +
@@ -1395,7 +1396,7 @@ public class PyTypingTest extends PyTestCase {
// PY-31004
public void testRecursiveTypeAliasInAnotherFile() {
doMultiFileStubAwareTest("Union[list, int]",
doMultiFileStubAwareTest("list | int",
"from other import MyType\n" +
"\n" +
"expr: MyType = ...");
@@ -1522,6 +1523,100 @@ public class PyTypingTest extends PyTestCase {
"expr: TypeAlias = int\n");
}
// PY-44974
public void testBitwiseOrUnionIsInstance() {
doTest("str | dict | int",
"a = [42]\n" +
"if isinstance(a, str | dict | int):\n" +
" expr = a");
}
// PY-44974
public void testBitwiseOrUnionIsSubclass() {
doTest("Type[str | dict | int]",
"a = list\n" +
"if issubclass(a, str | dict | int):\n" +
" expr = a");
}
// PY-44974
public void testBitwiseOrUnionIsInstanceIntNone() {
doTest("int | None",
"a = [42]\n" +
"if isinstance(a, int | None):\n" +
" expr = a");
}
// PY-44974
public void testBitwiseOrUnionIsInstanceNoneInt() {
doTest("int | None",
"a = [42]\n" +
"if isinstance(a, None | int):\n" +
" expr = a");
}
// PY-44974
public void testBitwiseOrUnionIsInstanceUnionInTuple() {
doTest("str | list | dict | bool | None",
"a = 42\n" +
"if isinstance(a, (str, (list | dict), bool | None)):\n" +
" expr = a");
}
// PY-44974
public void testBitwiseOrUnionOfUnionsIsInstance() {
doTest("dict | str | bool | list",
"from typing import Union\n" +
"a = 42\n" +
"if isinstance(a, Union[dict, Union[str, Union[bool, list]]]):\n" +
" expr = a");
}
// PY-44974
public void testBitwiseOrUnionWithFromFutureImport() {
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
doTest("int | str",
"from __future__ import annotations" + "\n" +
"if something:\n" +
" Type = int\n" +
"else:\n" +
" Type = str\n" +
"\n" +
"def f(expr: Type):\n" +
" pass\n");
});
}
// PY-44974
public void testWithoutFromFutureImport() {
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
doTest("Union[int, str]",
"if something:\n" +
" Type = int\n" +
"else:\n" +
" Type = str\n" +
"\n" +
"def f(expr: Type):\n" +
" pass\n");
});
}
// PY-44974
public void testBitwiseOrUnionParenthesizedUnionOfUnions() {
doTest("int | list | dict | float | str",
"bar: int | ((list | dict) | (float | str)) = \"\"\n" +
"expr = bar\n");
}
// PY-44974
public void testBitwiseOrOperatorOverload() {
doTest("int",
"class A:\n" +
" def __or__(self, other) -> int: return 5\n" +
" \n" +
"expr = A() | A()");
}
private void doTestNoInjectedText(@NotNull String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());

View File

@@ -508,6 +508,16 @@ public class PythonHighlightingTest extends PyTestCase {
doTest(LanguageLevel.PYTHON310, false, true);
}
// PY-44974
public void testBitwiseOrUnionInOlderVersionsError() {
doTest(LanguageLevel.PYTHON39, false, false);
}
// PY-44974
public void testBitwiseOrUnionInOlderVersionsErrorIsInstance() {
doTest(LanguageLevel.PYTHON39, false, false);
}
@NotNull
private static EditorColorsScheme createTemporaryColorScheme() {
EditorColorsManager manager = EditorColorsManager.getInstance();

View File

@@ -25,6 +25,46 @@ public class Py3CompatibilityInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-44974
public void testBitwiseOrUnionOnReturnType() {
doTestByText("def foo() -> <warning descr=\"Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y\">int | str</warning>:\n" +
" return 42\n");
}
// PY-44974
public void testBitwiseOrUnionOnReturnTypeFromFeatureAnnotations() {
doTestByText("from __future__ import annotations\n" +
"def foo() -> int | str:\n" +
" return 42\n");
}
// PY-44974
public void testBitwiseOrUnionOnIsInstance() {
doTestByText("class A:\n" +
" pass\n" +
"\n" +
"assert isinstance(A(), <warning descr=\"Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y\">int | str</warning>)\n");
}
// PY-44974
public void testBitwiseOrUnionInPrint() {
doTestByText("print(<warning descr=\"Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y\">int | str | dict</warning>)");
}
// PY-44974
public void testBitwiseOrUnionInOverloadedOperator() {
doTestByText("class A:\n" +
" def __or__(self, other) -> int: return 5\n" +
" \n" +
"expr = A() | A()");
}
// PY-44974
public void testBitwiseOrUnionInParenthesizedUnionOfUnions() {
doTestByText("def foo() -> <warning descr=\"Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y\">int | ((list | dict) | (float | str))</warning>:\n" +
" pass\n");
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {

View File

@@ -458,4 +458,27 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
" pass\n" +
"mymap(myfoo, [1, 2, 3])\n");
}
// PY-44974
public void testBitwiseOrUnionNoneIntStrAssignList() {
doTestByText("bar: None | int | str = <warning descr=\"Expected type 'None | int | str', got 'list[int]' instead\">[42]</warning>");
}
// PY-44974
public void testParenthesizedBitwiseOrUnionOfUnionsAssignNone() {
doTestByText("bar: int | ((list | dict) | (float | str)) = <warning descr=\"Expected type 'int | list | dict | float | str', got 'None' instead\">None</warning>");
}
// PY-44974
public void testTypingAndTypesBitwiseOrUnionDifference() {
doTestByText("from typing import Type\n" +
"def foo(x: Type[int | str]):\n" +
" pass\n" +
"foo(<warning descr=\"Expected type 'Type[int | str]', got 'Union' instead\">int | str</warning>)");
}
// PY-44974
public void testBitwiseOrUnionsAndOldStyleUnionsAreEquivalent() {
doTest();
}
}

View File

@@ -283,6 +283,31 @@ public class Py3UnresolvedReferencesInspectionTest extends PyInspectionTestCase
doTest();
}
// PY-44974
public void testNoInspectionInBitwiseOrUnionNoneInt() {
doTestByText("print(None | int)");
}
// PY-44974
public void testNoInspectionInBitwiseOrUnionIntNone() {
doTestByText("print(int | None)");
}
// PY-44974
public void testNoInspectionInBitwiseOrUnionIntStrNone() {
doTestByText("print(int | str | None)");
}
// PY-44974
public void testNoInspectionInBitwiseOrUnionNoneParIntStr() {
doTestByText("print(None | (int | str))");
}
// PY-44974
public void testNoInspectionInBitwiseOrUnionWithParentheses() {
doTestByText("bar: int | ((list | dict) | (float | str)) = \"\"");
}
public void testClassLevelDunderAll() {
doMultiFileTest("a.py");
}

View File

@@ -270,6 +270,57 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
}
// PY-28249
public void testInstanceAndClassChecksOnUnionBefore310() {
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
doTestByText("from typing import Union\n" +
"\n" +
"class A:\n" +
" pass\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union</error>)\n" +
"B = Union\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">B</error>)\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union[int, str]</error>)\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">B[int, str]</error>)\n" +
"C = B[int, str]\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">C</error>)\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union[str, Union[str, Union[list, dict]]]</error>)\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union[str, Union[str, Union[list[int], dict]]]</error>)\n" +
"assert isinstance(A(), <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | str</error>)\n" +
"assert isinstance(A(), <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | list[str]</error>)\n" +
"assert issubclass(A, <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | str</error>)\n" +
"assert issubclass(A, <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | list[str]</error>)");
});
}
// PY-44974
public void testInstanceAndClassChecksOnUnionFromFutureAnnotations() {
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
doTestByText("from typing import Union\n" +
"from __future__ import annotations\n" +
"\n" +
"class A:\n" +
" pass\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union</error>)\n" +
"B = Union\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">B</error>)\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union[int, str]</error>)\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">B[int, str]</error>)\n" +
"C = B[int, str]\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">C</error>)\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union[str, Union[str, Union[list, dict]]]</error>)\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union[str, Union[str, Union[list[int], dict]]]</error>)\n" +
"assert isinstance(A(), <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | str</error>)\n" +
"assert isinstance(A(), <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | list[str]</error>)\n" +
"assert issubclass(A, <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | str</error>)\n" +
"assert issubclass(A, <error descr=\"Python version 3.9 does not allow writing union types as X | Y\">int | list[str]</error>)");
});
}
// PY-44974
public void testInstanceAndClassChecksOnUnion() {
doTestByText("from typing import Union\n" +
"\n" +
@@ -280,10 +331,55 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
"B = Union\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">B</error>)\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Union' cannot be used with instance and class checks\">Union[int, str]</error>)\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">B[int, str]</error>)\n" +
"assert isinstance(A(), Union[int, str])\n" +
"assert issubclass(A, B[int, str])\n" +
"C = B[int, str]\n" +
"assert issubclass(A, <error descr=\"'Union' cannot be used with instance and class checks\">C</error>)");
"assert issubclass(A, C)\n" +
"assert isinstance(A(), Union[str, Union[str, Union[list, dict]]])\n" +
"assert isinstance(A(), Union[str, Union[str, Union[<error descr=\"Parameterized generics cannot be used with instance and class checks\">list[int]</error>, dict]]])\n" +
"assert isinstance(A(), int | str)\n" +
"assert isinstance(A(), int | <error descr=\"Parameterized generics cannot be used with instance and class checks\">list[str]</error>)\n" +
"assert issubclass(A, int | str)\n" +
"assert issubclass(A, int | <error descr=\"Parameterized generics cannot be used with instance and class checks\">list[str]</error>)\n");
}
// PY-28249
public void testInstanceAndClassChecksOnOptionalBefore310() {
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
doTestByText("from typing import Optional\n" +
"\n" +
"class A:\n" +
" pass\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Optional' cannot be used with instance and class checks\">Optional</error>)\n" +
"B = Optional\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">B</error>)\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Optional' cannot be used with instance and class checks\">Optional[int]</error>)\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">B[int]</error>)\n" +
"C = B[int]\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">C</error>)");
});
}
// PY-28249
public void testInstanceAndClassChecksOnOptionalFromFutureAnnotations() {
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
doTestByText("from typing import Optional\n" +
"from __future__ import annotations\n" +
"\n" +
"class A:\n" +
" pass\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Optional' cannot be used with instance and class checks\">Optional</error>)\n" +
"B = Optional\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">B</error>)\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Optional' cannot be used with instance and class checks\">Optional[int]</error>)\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">B[int]</error>)\n" +
"C = B[int]\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">C</error>)");
});
}
// PY-28249
@@ -297,10 +393,10 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
"B = Optional\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">B</error>)\n" +
"\n" +
"assert isinstance(A(), <error descr=\"'Optional' cannot be used with instance and class checks\">Optional[int]</error>)\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">B[int]</error>)\n" +
"assert isinstance(A(), Optional[int])\n" +
"assert issubclass(A, B[int])\n" +
"C = B[int]\n" +
"assert issubclass(A, <error descr=\"'Optional' cannot be used with instance and class checks\">C</error>)");
"assert issubclass(A, C)");
}
// PY-28249

View File

@@ -149,7 +149,7 @@ public class PyTypedDictInspectionTest extends PyInspectionTestCase {
"m = Horror(name='Alien', year=1979)\n" +
"d={'name':'Garden State', 'year':2004}\n" +
"m.update(d)\n" +
"m.update({'name':'Garden State', 'year':<warning descr=\"Expected type 'Optional[int]', got 'str' instead\">'2004'</warning>, <warning descr=\"TypedDict \\\"Horror\\\" cannot have key 'based_on'\">'based_on'</warning>: 'book'})\n" +
"m.update({'name':'Garden State', 'year':<warning descr=\"Expected type 'int | None', got 'str' instead\">'2004'</warning>, <warning descr=\"TypedDict \\\"Horror\\\" cannot have key 'based_on'\">'based_on'</warning>: 'book'})\n" +
"m.update(name=<warning descr=\"Expected type 'str', got 'int' instead\">1984</warning>, year=1984, based_on_book=<warning descr=\"Expected type 'bool', got 'str' instead\">'yes'</warning>)\n" +
"m.update([('name',<warning descr=\"Expected type 'str', got 'int' instead\">1984</warning>), ('year',None)])");
}

View File

@@ -88,7 +88,7 @@ public class PyiTypeTest extends PyTestCase {
}
public void testFunctionReturnType() {
doTest("Optional[int]");
doTest("int | None");
}
public void testFunctionType() {
@@ -114,12 +114,12 @@ public class PyiTypeTest extends PyTestCase {
// PY-22808
public void testOverloadedNotMatchedType() {
doTest("Union[list, Any]");
doTest("list | Any");
}
// PY-22808
public void testOverloadedNotMatchedGenericType() {
doTest("Union[dict[str, Any], list]");
doTest("dict[str, Any] | list");
}
public void testGenericClassDefinitionInOtherFile() {

View File

@@ -0,0 +1,63 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.quickFixes
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.PyQuickFixTestCase
import com.jetbrains.python.inspections.PyCompatibilityInspection
import com.jetbrains.python.psi.LanguageLevel
class PyReplaceWithOldStyleUnionQuickFixTest: PyQuickFixTestCase() {
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxInReturn() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxInReturnComplexType() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxInIsInstance() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxInIsSubclass() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxIntNoneWithOptional() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxNoneIntWithOptional() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxIntNoneStrWithUnion() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxIntStrNoneWithUnion() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
// PY-44974
fun testBitwiseOrUnionReplacedByOldSyntaxParenthesizedUnions() {
doQuickFixTest(PyCompatibilityInspection::class.java,
PyPsiBundle.message("QFIX.replace.with.old.union.style"))
}
}