mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 05:21:29 +07:00
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:
committed by
intellij-monorepo-bot
parent
51f447141a
commit
ebe8f93812
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Callable
|
||||
from typing import Callable, Any
|
||||
|
||||
|
||||
def func(x):
|
||||
|
||||
@@ -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>)
|
||||
|
||||
@@ -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>)
|
||||
@@ -0,0 +1,4 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
assert isinstance(A(), (int, str))
|
||||
@@ -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>)
|
||||
@@ -0,0 +1,4 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
assert issubclass(A, <caret>(int, str))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
def foo() -> Union[int, str]:
|
||||
return 42
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
def foo() -> <caret>Union[int, None, str]:
|
||||
return 42
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def foo() -> Opt<caret>ional[int]:
|
||||
return 42
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
def foo() -> <caret>Union[int, str, None]:
|
||||
return 42
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def foo() -> Optional[int]<caret>:
|
||||
return 42
|
||||
@@ -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>)
|
||||
@@ -0,0 +1,4 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
assert isinstance(A(), (int<caret>, str, list[str], bool, float, dict[str, int]))
|
||||
@@ -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" +
|
||||
|
||||
@@ -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, "});
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)])");
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user