mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-53104 Support PEP 673 typing.Self type
GitOrigin-RevId: 8efc65725580510dc3e9a93e1242e1a69032c8f1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3056a721f1
commit
cad1138e59
@@ -1131,6 +1131,7 @@ INSP.typeddict.required.notrequired.must.have.exactly.one.type.argument=''{0}''
|
||||
# PyTypeHintsInspection
|
||||
INSP.NAME.type.hints=Invalid type hints definitions and usages
|
||||
INSP.type.hints.builtin.cannot.be.parameterized.directly=Builtin ''{0}'' cannot be parameterized directly
|
||||
INSP.type.hints.typing.self.cannot.be.parameterized='Self' cannot be parameterized
|
||||
INSP.type.hints.invalid.type.self=Invalid type 'self'
|
||||
INSP.type.hints.literal.must.have.at.least.one.parameter='Literal' must have at least one parameter
|
||||
INSP.type.hints.annotated.must.be.called.with.at.least.two.arguments='Annotated' must be called with at least two arguments
|
||||
@@ -1167,6 +1168,10 @@ INSP.type.hints.type.alias.must.be.used.as.standalone.type.hint='TypeAlias' must
|
||||
INSP.type.hints.type.alias.must.be.immediately.initialized=Type alias must be immediately initialized
|
||||
INSP.type.hints.type.alias.must.be.top.level.declaration=Type alias must be top-level declaration
|
||||
INSP.type.hints.type.alias.cannot.be.parameterized='TypeAlias' cannot be parameterized
|
||||
INSP.type.hints.self.use.in.staticmethod=Cannot use 'Self' in staticmethod
|
||||
INSP.type.hints.self.use.outside.class=Cannot use 'Self' outside class
|
||||
INSP.type.hints.self.use.for.self.parameter.with.self.annotation=Cannot use 'Self' if 'self' parameter is not 'Self' annotated
|
||||
INSP.type.hints.self.use.for.cls.parameter.with.self.annotation=Cannot use 'Self' if 'cls' parameter is not 'Self' annotated
|
||||
QFIX.remove.function.annotations=Remove function annotations
|
||||
QFIX.replace.with.target.name=Replace with the target name
|
||||
QFIX.remove.generic.parameters=Remove generic parameters
|
||||
@@ -1223,4 +1228,4 @@ INSP.class.var.can.not.override.class.variable=Cannot override class variable ''
|
||||
INSP.class.var.can.not.override.instance.variable=Cannot override instance variable ''{0}'' (previously declared on base class ''{1}'') with class variable
|
||||
INSP.class.var.can.not.be.used.in.annotations.for.function.parameters='ClassVar' cannot be used in annotations for function parameters
|
||||
INSP.class.var.can.not.be.used.in.annotation.for.function.return.value='ClassVar' cannot be used in annotation for a function return value
|
||||
INSP.class.var.can.not.include.type.variables='ClassVar' parameter cannot include type variables
|
||||
INSP.class.var.can.not.include.type.variables='ClassVar' parameter cannot include type variables
|
||||
|
||||
+83
-12
@@ -3,6 +3,7 @@ package com.jetbrains.python.codeInsight.typing;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
@@ -17,6 +18,7 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import com.jetbrains.python.PyCustomType;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
@@ -108,6 +110,8 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
public static final String NOT_REQUIRED = "typing.NotRequired";
|
||||
public static final String NOT_REQUIRED_EXT = "typing_extensions.NotRequired";
|
||||
|
||||
public static final String SELF = "typing.Self";
|
||||
public static final String SELF_EXT = "typing_extensions.Self";
|
||||
private static final String PY2_FILE_TYPE = "typing.BinaryIO";
|
||||
private static final String PY3_BINARY_FILE_TYPE = "typing.BinaryIO";
|
||||
private static final String PY3_TEXT_FILE_TYPE = "typing.TextIO";
|
||||
@@ -192,8 +196,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
.add(TYPE_ALIAS, TYPE_ALIAS_EXT)
|
||||
.add(REQUIRED, REQUIRED_EXT)
|
||||
.add(NOT_REQUIRED, NOT_REQUIRED_EXT)
|
||||
.add(SELF, SELF_EXT)
|
||||
.build();
|
||||
|
||||
private static final Key<PsiElement> FRAGMENT_OWNER = Key.create("PY_FRAGMENT_OWNER");
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PyType getReferenceExpressionType(@NotNull PyReferenceExpression referenceExpression, @NotNull TypeEvalContext context) {
|
||||
@@ -680,6 +687,12 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
|
||||
final Iterable<QualifiedName> allBaseClassesQNames;
|
||||
final List<PySubscriptionExpression> subscriptedBaseClasses = PyClassElementType.getSubscriptedSuperClassesStubLike(pyClass);
|
||||
for (PySubscriptionExpression subscrExpr : subscriptedBaseClasses) {
|
||||
PsiFile containingFile = subscrExpr.getContainingFile();
|
||||
if (containingFile instanceof PyExpressionCodeFragment) {
|
||||
containingFile.putUserData(FRAGMENT_OWNER, pyClass);
|
||||
}
|
||||
}
|
||||
final Map<QualifiedName, PySubscriptionExpression> baseClassQNameToExpr = new HashMap<>();
|
||||
if (classStub == null) {
|
||||
allBaseClassesQNames = PyClassElementType.getSuperClassQNames(pyClass).keySet();
|
||||
@@ -753,20 +766,26 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
|
||||
@Nullable
|
||||
static Ref<PyType> getType(@NotNull PyExpression expression, @NotNull Context context) {
|
||||
final List<PyType> members = new ArrayList<>();
|
||||
boolean foundAny = false;
|
||||
for (Pair<PyTargetExpression, PsiElement> pair : tryResolvingWithAliases(expression, context.getTypeContext())) {
|
||||
final Ref<PyType> typeRef = getTypeForResolvedElement(pair.getFirst(), pair.getSecond(), context);
|
||||
if (typeRef != null) {
|
||||
final PyType type = typeRef.get();
|
||||
if (type == null) {
|
||||
foundAny = true;
|
||||
context.getExpressionStack().push(expression);
|
||||
try {
|
||||
final List<PyType> members = new ArrayList<>();
|
||||
boolean foundAny = false;
|
||||
for (Pair<PyTargetExpression, PsiElement> pair : tryResolvingWithAliases(expression, context.getTypeContext())) {
|
||||
final Ref<PyType> typeRef = getTypeForResolvedElement(pair.getFirst(), pair.getSecond(), context);
|
||||
if (typeRef != null) {
|
||||
final PyType type = typeRef.get();
|
||||
if (type == null) {
|
||||
foundAny = true;
|
||||
}
|
||||
members.add(type);
|
||||
}
|
||||
members.add(type);
|
||||
}
|
||||
final PyType union = PyUnionType.union(members);
|
||||
return union != null || foundAny ? Ref.create(union) : null;
|
||||
}
|
||||
finally {
|
||||
context.getExpressionStack().pop();
|
||||
}
|
||||
final PyType union = PyUnionType.union(members);
|
||||
return union != null || foundAny ? Ref.create(union) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -830,6 +849,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
context.getExpressionCache().add(alias);
|
||||
}
|
||||
context.getExpressionStack().push(resolved);
|
||||
try {
|
||||
final Ref<PyType> typeFromParenthesizedExpression = getTypeFromParenthesizedExpression(resolved, context);
|
||||
if (typeFromParenthesizedExpression != null) {
|
||||
@@ -924,15 +944,39 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
if (unionTypeFromBinaryOr != null) {
|
||||
return unionTypeFromBinaryOr;
|
||||
}
|
||||
final Ref<PyType> selfType = getSelfType(resolved, context);
|
||||
if (selfType != null) {
|
||||
return selfType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
context.getExpressionStack().pop();
|
||||
if (alias != null) {
|
||||
context.getExpressionCache().remove(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Ref<PyType> getSelfType(@NotNull PsiElement resolved, @NotNull Context context) {
|
||||
if (resolved instanceof PyQualifiedNameOwner &&
|
||||
(SELF.equals(((PyQualifiedNameOwner)resolved).getQualifiedName()) ||
|
||||
SELF_EXT.equals(((PyQualifiedNameOwner)resolved).getQualifiedName()))) {
|
||||
PsiElement lastTypeHintExpr = context.getExpressionStack().get(0);
|
||||
PsiElement typeHintContext = getStubRetainedTypeHintContext(lastTypeHintExpr);
|
||||
|
||||
PyClass containingClass = typeHintContext instanceof PyClass ? (PyClass)typeHintContext
|
||||
: PsiTreeUtil.getStubOrPsiParentOfType(typeHintContext, PyClass.class);
|
||||
if (containingClass == null) return null;
|
||||
|
||||
PyClassLikeType scopeClassType = containingClass.getType(context.getTypeContext());
|
||||
if (scopeClassType == null) return null;
|
||||
|
||||
return Ref.create(new PySelfType(scopeClassType));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Ref<PyType> getTypeFromBinaryExpression(@NotNull PsiElement resolved, @NotNull Context context) {
|
||||
if (resolved instanceof PyBinaryExpression) {
|
||||
@@ -1227,7 +1271,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
private static PyExpression toExpression(@NotNull String contents, @NotNull PsiElement anchor) {
|
||||
final PsiFile file = FileContextUtil.getContextFile(anchor);
|
||||
if (file == null) return null;
|
||||
return PyUtil.createExpressionFromFragment(contents, file);
|
||||
PyExpression fragment = PyUtil.createExpressionFromFragment(contents, file);
|
||||
if (fragment != null) {
|
||||
fragment.getContainingFile().putUserData(FRAGMENT_OWNER, anchor);
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -1498,6 +1546,22 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElement getStubRetainedTypeHintContext(@NotNull PsiElement typeHintExpression) {
|
||||
// Values from PSI stubs and regular type comments
|
||||
PsiElement fragmentOwner = typeHintExpression.getContainingFile().getUserData(FRAGMENT_OWNER);
|
||||
if (fragmentOwner != null) {
|
||||
return fragmentOwner;
|
||||
}
|
||||
// Values from function type comments
|
||||
else if (typeHintExpression.getContainingFile() instanceof PyFunctionTypeAnnotationFile) {
|
||||
return PyPsiUtils.getRealContext(typeHintExpression);
|
||||
}
|
||||
else {
|
||||
return typeHintExpression;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyParamSpecType getParamSpecType(@NotNull PsiElement element, @NotNull Context context) {
|
||||
if (!(element instanceof PyCallExpression)) return null;
|
||||
@@ -1830,6 +1894,8 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
@NotNull private final TypeEvalContext myContext;
|
||||
@NotNull private final Set<PsiElement> myCache = new HashSet<>();
|
||||
|
||||
@NotNull private final Stack<PsiElement> myExpressionStack = new Stack<>();
|
||||
|
||||
Context(@NotNull TypeEvalContext context) {
|
||||
myContext = context;
|
||||
}
|
||||
@@ -1843,5 +1909,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
public Set<PsiElement> getExpressionCache() {
|
||||
return myCache;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Stack<PsiElement> getExpressionStack() {
|
||||
return myExpressionStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
-1
@@ -167,6 +167,51 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPyAnnotation(node: PyAnnotation) {
|
||||
fun PyAnnotation.findSelvesInAnnotation(context: TypeEvalContext): List<PyReferenceExpression> =
|
||||
PsiTreeUtil.findChildrenOfAnyType(this.value, false, PyReferenceExpression::class.java).filter { refExpr ->
|
||||
PyTypingTypeProvider.resolveToQualifiedNames(refExpr, context).any {
|
||||
PyTypingTypeProvider.SELF == it || PyTypingTypeProvider.SELF_EXT == it
|
||||
}
|
||||
}
|
||||
|
||||
val selves = node.findSelvesInAnnotation(myTypeEvalContext)
|
||||
if (selves.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
fun registerProblemForSelves(message: String) {
|
||||
selves.forEach {
|
||||
registerProblem(it, message)
|
||||
}
|
||||
}
|
||||
|
||||
val classParent = PsiTreeUtil.getParentOfType(node, PyClass::class.java)
|
||||
if (classParent == null) {
|
||||
registerProblemForSelves(PyPsiBundle.message("INSP.type.hints.self.use.outside.class"))
|
||||
}
|
||||
|
||||
val functionParent = PsiTreeUtil.getParentOfType(node, PyFunction::class.java)
|
||||
if (functionParent != null) {
|
||||
if (PyFunction.Modifier.STATICMETHOD == functionParent.modifier) {
|
||||
registerProblemForSelves(PyPsiBundle.message("INSP.type.hints.self.use.in.staticmethod"))
|
||||
}
|
||||
|
||||
val parameters = functionParent.parameterList.parameters
|
||||
if (parameters.isNotEmpty()) {
|
||||
val firstParameter = parameters[0]
|
||||
val annotation = (firstParameter as? PyNamedParameter)?.annotation
|
||||
if (annotation != null && firstParameter.isSelf && annotation.findSelvesInAnnotation(myTypeEvalContext).isEmpty()) {
|
||||
val message = if (PyFunction.Modifier.CLASSMETHOD == functionParent.modifier)
|
||||
PyPsiBundle.message("INSP.type.hints.self.use.for.cls.parameter.with.self.annotation")
|
||||
else
|
||||
PyPsiBundle.message("INSP.type.hints.self.use.for.self.parameter.with.self.annotation")
|
||||
registerProblemForSelves(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPyFunction(node: PyFunction) {
|
||||
super.visitPyFunction(node)
|
||||
|
||||
@@ -365,7 +410,9 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
PyTypingTypeProvider.ANNOTATED,
|
||||
PyTypingTypeProvider.ANNOTATED_EXT,
|
||||
PyTypingTypeProvider.TYPE_ALIAS,
|
||||
PyTypingTypeProvider.TYPE_ALIAS_EXT -> {
|
||||
PyTypingTypeProvider.TYPE_ALIAS_EXT,
|
||||
PyTypingTypeProvider.SELF,
|
||||
PyTypingTypeProvider.SELF_EXT -> {
|
||||
val shortName = it.substringAfterLast('.')
|
||||
registerProblem(base, PyPsiBundle.message("INSP.type.hints.type.cannot.be.used.with.instance.class.checks", shortName),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
@@ -593,6 +640,8 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
val annotatedExtQName = QualifiedName.fromDottedString(PyTypingTypeProvider.ANNOTATED_EXT)
|
||||
val typeAliasQName = QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_ALIAS)
|
||||
val typeAliasExtQName = QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_ALIAS_EXT)
|
||||
val typingSelf = QualifiedName.fromDottedString(PyTypingTypeProvider.SELF)
|
||||
val typingExtSelf = QualifiedName.fromDottedString(PyTypingTypeProvider.SELF_EXT)
|
||||
val qNames = PyResolveUtil.resolveImportedElementQNameLocally(operand)
|
||||
|
||||
var typingOnly = true
|
||||
@@ -604,6 +653,7 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
literalQName, literalExtQName -> checkLiteralParameter(index)
|
||||
annotatedQName, annotatedExtQName -> checkAnnotatedParameter(index)
|
||||
typeAliasQName, typeAliasExtQName -> reportParameterizedTypeAlias(index)
|
||||
typingSelf, typingExtSelf -> reportParameterizedSelf(index)
|
||||
callableQName -> {
|
||||
callableExists = true
|
||||
checkCallableParameters(index)
|
||||
@@ -626,6 +676,11 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportParameterizedSelf(index: PyExpression) {
|
||||
registerProblem(index, PyPsiBundle.message("INSP.type.hints.typing.self.cannot.be.parameterized"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
|
||||
private fun checkLiteralParameter(index: PyExpression) {
|
||||
val subParameter = if (index is PySubscriptionExpression) index.operand else null
|
||||
if (subParameter is PyReferenceExpression &&
|
||||
|
||||
+1
-1
@@ -1220,7 +1220,7 @@ public final class PyCallExpressionHelper {
|
||||
}
|
||||
allMappedParameters.putAll(mappedExplicitParameters);
|
||||
|
||||
return PyTypeChecker.unifyGenericCallWithParamSpecs(receiver, allMappedParameters, context) == null;
|
||||
return PyTypeChecker.unifyGenericCall(receiver, allMappedParameters, context) == null;
|
||||
}
|
||||
|
||||
public static class ArgumentMappingResults {
|
||||
|
||||
@@ -254,7 +254,7 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
|
||||
@NotNull Map<PyExpression, PyCallableParameter> parameters,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (PyTypeChecker.hasGenerics(type, context)) {
|
||||
final var substitutions = PyTypeChecker.unifyGenericCallWithParamSpecs(receiver, parameters, context);
|
||||
final var substitutions = PyTypeChecker.unifyGenericCall(receiver, parameters, context);
|
||||
if (substitutions != null) {
|
||||
final var substitutionsWithUnresolvedReturnGenerics =
|
||||
PyTypeChecker.getSubstitutionsWithUnresolvedReturnGenerics(getParameters(context), type, substitutions, context);
|
||||
|
||||
+1
-1
@@ -405,7 +405,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
boolean possiblyParameterizedQualifier = !(qualifierType instanceof PyModuleType || qualifierType instanceof PyImportedModuleType);
|
||||
if (possiblyParameterizedQualifier && PyTypeChecker.hasGenerics(type, context)) {
|
||||
final var substitutions =
|
||||
PyTypeChecker.unifyGenericCallWithParamSpecs(qualifier, Collections.emptyMap(), context);
|
||||
PyTypeChecker.unifyGenericCall(qualifier, Collections.emptyMap(), context);
|
||||
if (substitutions != null) {
|
||||
final PyType substituted = PyTypeChecker.substitute(type, substitutions, context);
|
||||
if (substituted != null) {
|
||||
|
||||
@@ -14,7 +14,9 @@ import com.jetbrains.python.psi.resolve.RatedResolveResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@@ -67,7 +69,7 @@ public class PyCallableTypeImpl implements PyCallableType {
|
||||
@NotNull Collection<PyCallableParameter> allParameters,
|
||||
@Nullable PyExpression receiver,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final var substitutions = PyTypeChecker.unifyGenericCallWithParamSpecs(receiver, actualParameters, context);
|
||||
final var substitutions = PyTypeChecker.unifyGenericCall(receiver, actualParameters, context);
|
||||
final var substitutionsWithUnresolvedReturnGenerics =
|
||||
PyTypeChecker.getSubstitutionsWithUnresolvedReturnGenerics(allParameters, type, substitutions, context);
|
||||
return PyTypeChecker.substitute(type, substitutionsWithUnresolvedReturnGenerics, context);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.jetbrains.python.psi.types;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.jetbrains.python.psi.AccessDirection;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.RatedResolveResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class PySelfType implements PyType {
|
||||
@NotNull private final PyClassLikeType myScopeClassType;
|
||||
|
||||
public PySelfType(@NotNull PyClassLikeType scopeClassType) {
|
||||
myScopeClassType = scopeClassType.toInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<? extends RatedResolveResult> resolveMember(@NotNull String name,
|
||||
@Nullable PyExpression location,
|
||||
@NotNull AccessDirection direction,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
return myScopeClassType.resolveMember(name, location, direction, resolveContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getCompletionVariants(String completionPrefix,
|
||||
PsiElement location,
|
||||
ProcessingContext context) {
|
||||
return myScopeClassType.getCompletionVariants(completionPrefix, location, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return "Self";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyClassLikeType getScopeClassType() {
|
||||
return myScopeClassType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
PySelfType type = (PySelfType)o;
|
||||
|
||||
if (!Objects.equals(myScopeClassType, type.myScopeClassType)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myScopeClassType.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuiltin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertValid(String message) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public final class PyTypeChecker {
|
||||
@Nullable PyType actual,
|
||||
@NotNull TypeEvalContext context,
|
||||
@NotNull GenericSubstitutions substitutions) {
|
||||
return match(expected, actual, new MatchContext(context, substitutions.typeVars, substitutions.paramSpecs, false))
|
||||
return match(expected, actual, new MatchContext(context, substitutions, false))
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public final class PyTypeChecker {
|
||||
@NotNull
|
||||
private static Optional<Boolean> matchImpl(@Nullable PyType expected, @Nullable PyType actual, @NotNull MatchContext context) {
|
||||
for (PyTypeCheckerExtension extension : PyTypeCheckerExtension.EP_NAME.getExtensionList()) {
|
||||
final Optional<Boolean> result = extension.match(expected, actual, context.context, context.genericSubstitutions);
|
||||
final Optional<Boolean> result = extension.match(expected, actual, context.context, context.mySubstitutions.typeVars);
|
||||
if (result.isPresent()) {
|
||||
return result;
|
||||
}
|
||||
@@ -124,6 +124,14 @@ public final class PyTypeChecker {
|
||||
return Optional.of(match((PyGenericType)expected, actual, context));
|
||||
}
|
||||
|
||||
if (expected instanceof PySelfType) {
|
||||
return match(context.mySubstitutions.qualifierType, actual, context);
|
||||
}
|
||||
|
||||
if (actual instanceof PySelfType && context.reversedSubstitutions) {
|
||||
return match(context.mySubstitutions.qualifierType, expected, context);
|
||||
}
|
||||
|
||||
if (expected instanceof PyParamSpecType) {
|
||||
return Optional.of(match((PyParamSpecType)expected, actual, context));
|
||||
}
|
||||
@@ -215,7 +223,7 @@ public final class PyTypeChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PyType substitution = context.genericSubstitutions.get(expected);
|
||||
final PyType substitution = context.mySubstitutions.typeVars.get(expected);
|
||||
PyType bound = expected.getBound();
|
||||
// Promote int in Type[TypeVar('T', int)] to Type[int] before checking that bounds match
|
||||
if (expected.isDefinition()) {
|
||||
@@ -242,10 +250,10 @@ public final class PyTypeChecker {
|
||||
}
|
||||
|
||||
if (actual != null) {
|
||||
context.genericSubstitutions.put(expected, actual);
|
||||
context.mySubstitutions.typeVars.put(expected, actual);
|
||||
}
|
||||
else if (bound != null) {
|
||||
context.genericSubstitutions.put(expected, PyUnionType.createWeakType(bound));
|
||||
context.mySubstitutions.typeVars.put(expected, PyUnionType.createWeakType(bound));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -257,7 +265,7 @@ public final class PyTypeChecker {
|
||||
final var callableActual = (PyParamSpecType)actual;
|
||||
final var parameters = callableActual.getParameters();
|
||||
if (parameters == null) return false;
|
||||
context.paramSpecSubstitutions.put(expected, expected.withParameters(parameters, context.context));
|
||||
context.mySubstitutions.paramSpecs.put(expected, expected.withParameters(parameters, context.context));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -349,8 +357,17 @@ public final class PyTypeChecker {
|
||||
.map(context::getType)
|
||||
.anyMatch(
|
||||
subclassElementType -> {
|
||||
return match(protocolElementType,
|
||||
dropSelfIfNeeded(actual, subclassElementType, context), matchContext).orElse(true);
|
||||
var matched = match(protocolElementType,
|
||||
dropSelfIfNeeded(actual, subclassElementType, context), matchContext).orElse(true);
|
||||
if (!matched) return false;
|
||||
if (!(protocolElementType instanceof PyFunctionType) || !(subclassElementType instanceof PyFunctionType)) return matched;
|
||||
var protocolReturnType = ((PyFunctionType)protocolElementType).getReturnType(context);
|
||||
if (protocolReturnType instanceof PySelfType) {
|
||||
var subclassReturnType = ((PyFunctionType)subclassElementType).getReturnType(context);
|
||||
if (subclassReturnType instanceof PySelfType) return true;
|
||||
return match(actual, subclassReturnType, matchContext).orElse(true);
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -509,7 +526,7 @@ public final class PyTypeChecker {
|
||||
// TODO: Check named and star params, not only positional ones
|
||||
if (expectedParamType instanceof PyParamSpecType && expectedParameters.size() == 1) {
|
||||
final var expectedParamSpecType = (PyParamSpecType)expectedParamType;
|
||||
matchContext.paramSpecSubstitutions.put(expectedParamSpecType, expectedParamSpecType.withParameters(actualParameters, context));
|
||||
matchContext.mySubstitutions.paramSpecs.put(expectedParamSpecType, expectedParamSpecType.withParameters(actualParameters, context));
|
||||
break;
|
||||
}
|
||||
else if (expectedParamType instanceof PyConcatenateType && expectedParameters.size() == 1) {
|
||||
@@ -539,7 +556,7 @@ public final class PyTypeChecker {
|
||||
final var expectedParamSpecType = expectedConcatenateType.getParamSpec();
|
||||
final var restActualParameters = actualParameters.subList(actualParamRightBound, actualParameters.size());
|
||||
final var parametersSubst = expectedParamSpecType.withParameters(restActualParameters, context);
|
||||
matchContext.paramSpecSubstitutions.put(expectedParamSpecType, parametersSubst);
|
||||
matchContext.mySubstitutions.paramSpecs.put(expectedParamSpecType, parametersSubst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +796,9 @@ public final class PyTypeChecker {
|
||||
if (type instanceof PyConcatenateType) {
|
||||
generics.concatenates.add((PyConcatenateType)type);
|
||||
}
|
||||
if (type instanceof PySelfType) {
|
||||
generics.self = (PySelfType)type;
|
||||
}
|
||||
else if (type instanceof PyUnionType) {
|
||||
final PyUnionType union = (PyUnionType)type;
|
||||
for (PyType t : union.getMembers()) {
|
||||
@@ -819,7 +839,7 @@ public final class PyTypeChecker {
|
||||
@Nullable
|
||||
public static PyType substitute(@Nullable PyType type, @NotNull Map<PyGenericType, PyType> substitutions,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final var genericSubstitutions = new GenericSubstitutions(substitutions, new LinkedHashMap<>());
|
||||
final var genericSubstitutions = new GenericSubstitutions(substitutions, new LinkedHashMap<>(), null);
|
||||
return substitute(type, genericSubstitutions, context);
|
||||
}
|
||||
|
||||
@@ -843,6 +863,13 @@ public final class PyTypeChecker {
|
||||
}
|
||||
return substitution;
|
||||
}
|
||||
else if (type instanceof PySelfType) {
|
||||
var qualifierType = substitutions.qualifierType;
|
||||
var selfScopeClassType = ((PySelfType)type).getScopeClassType();
|
||||
return PyTypeUtil.toStream(qualifierType)
|
||||
.filter(memberType -> match(selfScopeClassType, memberType, context))
|
||||
.collect(PyTypeUtil.toUnion());
|
||||
}
|
||||
else if (type instanceof PyUnionType) {
|
||||
return ((PyUnionType)type).map(member -> substitute(member, substitutions, context));
|
||||
}
|
||||
@@ -919,23 +946,10 @@ public final class PyTypeChecker {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link PyTypeChecker#unifyGenericCallWithParamSpecs(PyExpression, Map, TypeEvalContext)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public static Map<PyGenericType, PyType> unifyGenericCall(@Nullable PyExpression receiver,
|
||||
@NotNull Map<PyExpression, PyCallableParameter> arguments,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final var result = unifyGenericCallWithParamSpecs(receiver, arguments, context);
|
||||
if (result == null) return null;
|
||||
return result.typeVars;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static GenericSubstitutions unifyGenericCallWithParamSpecs(@Nullable PyExpression receiver,
|
||||
@NotNull Map<PyExpression, PyCallableParameter> arguments,
|
||||
@NotNull TypeEvalContext context) {
|
||||
public static GenericSubstitutions unifyGenericCall(@Nullable PyExpression receiver,
|
||||
@NotNull Map<PyExpression, PyCallableParameter> arguments,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final var substitutions = unifyReceiverWithParamSpecs(receiver, context);
|
||||
for (Map.Entry<PyExpression, PyCallableParameter> entry : getRegularMappedParameters(arguments).entrySet()) {
|
||||
final PyCallableParameter paramWrapper = entry.getValue();
|
||||
@@ -1001,6 +1015,10 @@ public final class PyTypeChecker {
|
||||
// Collect generic params of object type
|
||||
final var substitutions = new GenericSubstitutions();
|
||||
final PyType qualifierType = receiver != null ? context.getType(receiver) : null;
|
||||
substitutions.qualifierType = qualifierType;
|
||||
if (qualifierType instanceof PyClassType) {
|
||||
substitutions.qualifierType = ((PyClassType)qualifierType).toInstance();
|
||||
}
|
||||
final var generics = collectGenerics(qualifierType, context);
|
||||
for (PyGenericType t : generics.typeVars) {
|
||||
substitutions.typeVars.put(t, t);
|
||||
@@ -1222,7 +1240,7 @@ public final class PyTypeChecker {
|
||||
for (int i = 0; i < Math.min(formalTypeParams.size(), actualTypeParams.size()); i++) {
|
||||
substitutions.put(formalTypeParams.get(i), actualTypeParams.get(i));
|
||||
}
|
||||
return substitute(genericType, new GenericSubstitutions(substitutions, Collections.emptyMap()), context);
|
||||
return substitute(genericType, new GenericSubstitutions(substitutions, Collections.emptyMap(), null), context);
|
||||
}
|
||||
// An already parameterized type, don't override existing values for type parameters
|
||||
else if (genericType instanceof PyCollectionType) {
|
||||
@@ -1247,18 +1265,25 @@ public final class PyTypeChecker {
|
||||
@NotNull
|
||||
private final Set<PyConcatenateType> concatenates;
|
||||
|
||||
@Nullable
|
||||
private PySelfType self;
|
||||
|
||||
Generics() {
|
||||
this(new LinkedHashSet<>(), new LinkedHashSet<>(), new LinkedHashSet<>());
|
||||
this(new LinkedHashSet<>(), new LinkedHashSet<>(), new LinkedHashSet<>(), null);
|
||||
}
|
||||
|
||||
Generics(@NotNull Set<PyGenericType> generics, @NotNull Set<PyParamSpecType> paramSpecs, @NotNull Set<PyConcatenateType> concatenates) {
|
||||
Generics(@NotNull Set<PyGenericType> generics,
|
||||
@NotNull Set<PyParamSpecType> paramSpecs,
|
||||
@NotNull Set<PyConcatenateType> concatenates,
|
||||
@Nullable PySelfType self) {
|
||||
this.typeVars = generics;
|
||||
this.paramSpecs = paramSpecs;
|
||||
this.concatenates = concatenates;
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return typeVars.isEmpty() && paramSpecs.isEmpty() && concatenates.isEmpty();
|
||||
return typeVars.isEmpty() && paramSpecs.isEmpty() && concatenates.isEmpty() && self == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1269,14 +1294,23 @@ public final class PyTypeChecker {
|
||||
@NotNull
|
||||
private final Map<PyParamSpecType, PyParamSpecType> paramSpecs;
|
||||
|
||||
@Nullable
|
||||
private PyType qualifierType;
|
||||
|
||||
GenericSubstitutions() {
|
||||
this(new LinkedHashMap<>(), new LinkedHashMap<>());
|
||||
this(new LinkedHashMap<>(), new LinkedHashMap<>(), null);
|
||||
}
|
||||
|
||||
GenericSubstitutions(@NotNull Map<PyGenericType, PyType> typeVars) {
|
||||
this(typeVars, new LinkedHashMap<>(), null);
|
||||
}
|
||||
|
||||
GenericSubstitutions(@NotNull Map<PyGenericType, PyType> typeVars,
|
||||
@NotNull Map<PyParamSpecType, PyParamSpecType> paramSpecs) {
|
||||
@NotNull Map<PyParamSpecType, PyParamSpecType> paramSpecs,
|
||||
@Nullable PyType qualifierType) {
|
||||
this.typeVars = typeVars;
|
||||
this.paramSpecs = paramSpecs;
|
||||
this.qualifierType = qualifierType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -1284,9 +1318,9 @@ public final class PyTypeChecker {
|
||||
return paramSpecs;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<PyGenericType, PyType> getTypeVars() {
|
||||
return typeVars;
|
||||
@Nullable
|
||||
public PyType getQualifierType() {
|
||||
return qualifierType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1296,37 +1330,24 @@ public final class PyTypeChecker {
|
||||
private final TypeEvalContext context;
|
||||
|
||||
@NotNull
|
||||
private final Map<PyGenericType, PyType> genericSubstitutions; // mutable
|
||||
|
||||
@NotNull
|
||||
private final Map<PyParamSpecType, PyParamSpecType> paramSpecSubstitutions; // mutable
|
||||
private final GenericSubstitutions mySubstitutions;
|
||||
|
||||
private final boolean reversedSubstitutions;
|
||||
|
||||
MatchContext(@NotNull TypeEvalContext context,
|
||||
@NotNull Map<PyGenericType, PyType> genericSubstitutions) {
|
||||
this(context, genericSubstitutions, false);
|
||||
@NotNull Map<PyGenericType, PyType> typeVars) {
|
||||
this(context, new GenericSubstitutions(typeVars), false);
|
||||
}
|
||||
|
||||
private MatchContext(@NotNull TypeEvalContext context,
|
||||
@NotNull Map<PyGenericType, PyType> genericSubstitutions,
|
||||
boolean reversedSubstitutions) {
|
||||
this(context, genericSubstitutions, new HashMap<>(), reversedSubstitutions);
|
||||
}
|
||||
|
||||
private MatchContext(@NotNull TypeEvalContext context,
|
||||
@NotNull Map<PyGenericType, PyType> genericSubstitutions,
|
||||
@NotNull Map<PyParamSpecType, PyParamSpecType> paramSpecSubstitutions,
|
||||
boolean reversedSubstitutions) {
|
||||
MatchContext(@NotNull TypeEvalContext context, @NotNull GenericSubstitutions substitutions, boolean reversedSubstitutions) {
|
||||
this.context = context;
|
||||
this.genericSubstitutions = genericSubstitutions;
|
||||
this.paramSpecSubstitutions = paramSpecSubstitutions;
|
||||
this.mySubstitutions = substitutions;
|
||||
this.reversedSubstitutions = reversedSubstitutions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MatchContext reverseSubstitutions() {
|
||||
return new MatchContext(context, genericSubstitutions, !reversedSubstitutions);
|
||||
return new MatchContext(context, mySubstitutions, !reversedSubstitutions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from typing import Self
|
||||
|
||||
class A:
|
||||
def foo(self) -> list[Self]:
|
||||
...
|
||||
class B(A)
|
||||
def bar(self) -> Self:
|
||||
...
|
||||
B().foo()[0].<caret>
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import Self
|
||||
|
||||
class OuterClass:
|
||||
class A:
|
||||
def foo(self) -> list[Self]:
|
||||
...
|
||||
class B(A)
|
||||
def bar(self) -> Self:
|
||||
...
|
||||
OuterClass.B().foo()[0].<caret>
|
||||
@@ -0,0 +1,9 @@
|
||||
from typing import Self
|
||||
|
||||
class A:
|
||||
def foo(self) -> Self:
|
||||
...
|
||||
class B(A)
|
||||
def bar(self) -> Self:
|
||||
...
|
||||
B().foo().<caret>
|
||||
@@ -0,0 +1,8 @@
|
||||
from typing import Self
|
||||
|
||||
|
||||
class HasNestedFunction:
|
||||
x: int = 42
|
||||
|
||||
def foo(self, inner_self: Self) -> None:
|
||||
print(inner_self.<caret>)
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import Self
|
||||
|
||||
|
||||
class HasNestedFunction:
|
||||
x: int = 42
|
||||
|
||||
def foo(self) -> None:
|
||||
def nested(z: int, inner_self: Self) -> Self:
|
||||
print(inner_self.<caret>)
|
||||
return inner_self
|
||||
|
||||
nested(42, self)
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><div class='definition'><pre>Self.A<br>def <b>foo</b>(self) -> <a href="psi_element://#typename#Self">Self</a></pre></div></body></html>
|
||||
@@ -0,0 +1,9 @@
|
||||
from typing import Self
|
||||
|
||||
class A:
|
||||
def foo(self) -> Self:
|
||||
...
|
||||
class B(A):
|
||||
def bar(self) -> Self:
|
||||
...
|
||||
B().f<the_ref>oo()
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#typename#TypingExtensionsSelf.A">TypingExtensionsSelf.A</a><br>def <b>foo</b>(self) -> Self</pre></div></body></html>
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing_extensions import Self
|
||||
|
||||
class A:
|
||||
def foo(self) -> Self:
|
||||
...
|
||||
class B(A):
|
||||
def bar(self) -> Self:
|
||||
...
|
||||
b = B()
|
||||
b.f<the_ref>oo()
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
from typing import Self
|
||||
|
||||
class Clazz:
|
||||
def foo(self) -> Self:
|
||||
return self
|
||||
@@ -658,6 +658,39 @@ public class Py3CompletionTest extends PyTestCase {
|
||||
doTabTest();
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testOnSelf() {
|
||||
doTestVariants("foo", "bar");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testOnListSelf() {
|
||||
doTestVariants("foo", "bar");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testOnListSelfInNestedClass() {
|
||||
doTestVariants("foo", "bar");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testOnSelfInsideClassFunction() {
|
||||
doTestVariants("x", "foo");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testOnSelfInsideClassFunctionNestedFunction() {
|
||||
doTestVariants("x", "foo");
|
||||
}
|
||||
|
||||
private void doTestVariants(String @NotNull ... expected) {
|
||||
final String testName = getTestName(true);
|
||||
myFixture.configureByFile(testName + ".py");
|
||||
myFixture.completeBasic();
|
||||
List<String> variants = myFixture.getLookupElementStrings();
|
||||
assertContainsElements(variants, expected);
|
||||
}
|
||||
|
||||
private @NotNull PyCodeStyleSettings getPythonCodeStyleSettings() {
|
||||
return getCodeStyleSettings().getCustomSettings(PyCodeStyleSettings.class);
|
||||
}
|
||||
|
||||
@@ -91,6 +91,16 @@ public class Py3QuickDocTest extends LightMarkedTestCase {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testSelf() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingExtensionsSelf() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return super.getTestDataPath() + "/quickdoc/";
|
||||
|
||||
@@ -18,7 +18,9 @@ package com.jetbrains.python;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
@@ -2013,6 +2015,98 @@ public class PyTypingTest extends PyTestCase {
|
||||
expr = A.x""");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testMethodReturnSelf() {
|
||||
doTest("B",
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"class A:\n" +
|
||||
" def foo(self) -> Self:\n" +
|
||||
" ...\n" +
|
||||
"class B(A):\n" +
|
||||
" pass\n" +
|
||||
"expr = B().foo()");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testMethodReturnListSelf() {
|
||||
doTest("list[B]",
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"class A:\n" +
|
||||
" def foo(self) -> list[Self]:\n" +
|
||||
" ...\n" +
|
||||
"class B(A):\n" +
|
||||
" pass:\n" +
|
||||
" ...\n" +
|
||||
"expr = B().foo()");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testClassMethodReturnSelf() {
|
||||
doTest("Circle",
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Shape:\n" +
|
||||
" @classmethod\n" +
|
||||
" def from_config(cls, config: dict[str, float]) -> Self:\n" +
|
||||
" return cls(config[\"scale\"])\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Circle(Shape):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Circle.from_config({})\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testClassMethodReturnSelfNestedClass() {
|
||||
doTest("Circle",
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class OuterClass:\n" +
|
||||
" class Shape:\n" +
|
||||
" @classmethod\n" +
|
||||
" def from_config(cls, config: dict[str, float]) -> Self:\n" +
|
||||
" return cls(config[\"scale\"])\n" +
|
||||
"\n" +
|
||||
" class Circle(Shape):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = OuterClass.Circle.from_config({})\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testNoUnstubInCalculateSelfTypeInFunctionDefinedInImportedFile() {
|
||||
doMultiFileStubAwareTest("Clazz",
|
||||
"from other import Clazz\n" +
|
||||
"clz = Clazz()\n" +
|
||||
"expr = clz.foo()\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testMatchSelfUnionType() {
|
||||
doTest("C",
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class C:\n" +
|
||||
" def method(self) -> Self:\n" +
|
||||
" return self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"if bool():\n" +
|
||||
" x = 42\n" +
|
||||
"else:\n" +
|
||||
" x = C()\n" +
|
||||
"\n" +
|
||||
"expr = x.method()");
|
||||
}
|
||||
|
||||
private void doTestNoInjectedText(@NotNull String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());
|
||||
|
||||
@@ -962,4 +962,300 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
public void testListLiteralPassedToIter() {
|
||||
doTestByText("iter([1, 2, 3])");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testParameterSelf() {
|
||||
doTestByText("from typing import Self, Callable\n" +
|
||||
"\n" +
|
||||
"class Shape:\n" +
|
||||
" def difference(self, other: Self) -> float: ...\n" +
|
||||
"\n" +
|
||||
" def apply(self, f: Callable[[Self], None]) -> None: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Circle(Shape):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def fCircle(c: Circle):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def fShape(sh: Shape):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"sh = Shape()\n" +
|
||||
"cir = Circle()\n" +
|
||||
"\n" +
|
||||
"sh.difference(cir)\n" +
|
||||
"sh.difference(sh)\n" +
|
||||
"cir.difference(cir)\n" +
|
||||
"cir.difference(<warning descr=\"Expected type 'Circle' (matched generic type 'Self'), got 'Shape' instead\">sh</warning>)\n" +
|
||||
"\n" +
|
||||
"cir.apply(fCircle)\n" +
|
||||
"cir.apply(<warning descr=\"Expected type '(Circle) -> None' (matched generic type '(Self) -> None'), got '(sh: Shape) -> None' instead\">fShape</warning>)\n" +
|
||||
"sh.apply(fCircle)\n" +
|
||||
"sh.apply(fShape)");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testParameterTypeSelf() {
|
||||
doTestByText("from typing import Self, Callable\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: Type[Self]) -> None: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class SubClass(MyClass):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"myClass = MyClass()\n" +
|
||||
"subClass = MySubClass()\n" +
|
||||
"\n" +
|
||||
"myClass.foo(myClass)\n" +
|
||||
"myClass.foo(subClass)\n" +
|
||||
"myClass.foo(MyClass)\n" +
|
||||
"myClass.foo(SubClass)\n" +
|
||||
"\n" +
|
||||
"subClass.foo(myClass)\n" +
|
||||
"subClass.foo(subClass)\n" +
|
||||
"subClass.foo(MyClass)\n" +
|
||||
"subClass.foo(SubClass)");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testParameterTypeSelfUnion() {
|
||||
doTestByText("from typing import Self, Callable\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: Self | None | int) -> None: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class SubClass(MyClass):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"myClass = MyClass()\n" +
|
||||
"subClass = SubClass()\n" +
|
||||
"\n" +
|
||||
"myClass.foo(myClass)\n" +
|
||||
"myClass.foo(subClass)\n" +
|
||||
"myClass.foo(42)\n" +
|
||||
"myClass.foo(None)\n" +
|
||||
"myClass.foo(<warning descr=\"Expected type 'MyClass | None | int' (matched generic type 'Self | None | int'), got 'str' instead\">\"\"</warning>)\n" +
|
||||
"\n" +
|
||||
"subClass.foo(<warning descr=\"Expected type 'SubClass | None | int' (matched generic type 'Self | None | int'), got 'MyClass' instead\">myClass</warning>)\n" +
|
||||
"subClass.foo(subClass)\n" +
|
||||
"subClass.foo(42)\n" +
|
||||
"subClass.foo(None)\n" +
|
||||
"subClass.foo(<warning descr=\"Expected type 'SubClass | None | int' (matched generic type 'Self | None | int'), got 'str' instead\">\"\"</warning>)");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testParameterTypeSelfReturnAsParameter() {
|
||||
doTestByText("from typing import Self, Callable\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: Self) -> Self: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class SubClass(MyClass):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"myClass = MyClass()\n" +
|
||||
"subClass = SubClass()\n" +
|
||||
"\n" +
|
||||
"myClass.foo(myClass.foo(myClass))\n" +
|
||||
"myClass.foo(subClass.foo(subClass))\n" +
|
||||
"myClass.foo(myClass.foo(subClass))\n" +
|
||||
"myClass.foo(subClass.foo(<warning descr=\"Expected type 'SubClass' (matched generic type 'Self'), got 'MyClass' instead\">myClass</warning>))\n" +
|
||||
"\n" +
|
||||
"subClass.foo(<warning descr=\"Expected type 'SubClass' (matched generic type 'Self'), got 'MyClass' instead\">myClass.foo(myClass)</warning>)\n" +
|
||||
"subClass.foo(subClass.foo(subClass))\n" +
|
||||
"subClass.foo(<warning descr=\"Expected type 'SubClass' (matched generic type 'Self'), got 'MyClass' instead\">myClass.foo(subClass)</warning>)\n" +
|
||||
"subClass.foo(subClass.foo(<warning descr=\"Expected type 'SubClass' (matched generic type 'Self'), got 'MyClass' instead\">myClass</warning>))");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testProtocolSelfClass() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self, Protocol\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyProtocol(Protocol):\n" +
|
||||
" def foo(self, bar: float) -> Self: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: float) -> MyClass:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def accepts_protocol(obj: MyProtocol) -> None:\n" +
|
||||
" print(obj)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"obj = MyClass()\n" +
|
||||
"accepts_protocol(obj)\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testProtocolSelfSubclass() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self, Protocol\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyProtocol(Protocol):\n" +
|
||||
" def foo(self, bar: float) -> Self: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: float) -> MySubClass:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MySubClass(MyClass):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def accepts_protocol(obj: MyProtocol) -> None:\n" +
|
||||
" print(obj)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"obj = MyClass()\n" +
|
||||
"accepts_protocol(obj)\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testProtocolSelfOtherClass() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self, Protocol\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyProtocol(Protocol):\n" +
|
||||
" def foo(self, bar: float) -> Self: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: float) -> int:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def accepts_protocol(obj: MyProtocol) -> None:\n" +
|
||||
" print(obj)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"obj = MyClass()\n" +
|
||||
"accepts_protocol(<warning descr=\"Expected type 'MyProtocol', got 'MyClass' instead\">obj</warning>)\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testProtocolSelfNotSubclass() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self, Protocol\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyProtocol(Protocol):\n" +
|
||||
" def foo(self, bar: float) -> Self: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: float) -> MyClassNotSubclass:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyClassNotSubclass:\n" +
|
||||
" def foo(self, bar: float) -> int:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def accepts_protocol(obj: MyProtocol) -> None:\n" +
|
||||
" print(obj)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"obj = MyClass()\n" +
|
||||
"accepts_protocol(<warning descr=\"Expected type 'MyProtocol', got 'MyClass' instead\">obj</warning>)\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testProtocolSelfSelf() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self, Protocol\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyProtocol(Protocol):\n" +
|
||||
" def foo(self, bar: float) -> Self: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class MyClass:\n" +
|
||||
" def foo(self, bar: float) -> Self:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def accepts_protocol(obj: MyProtocol) -> None:\n" +
|
||||
" print(obj)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"obj = MyClass()\n" +
|
||||
"accepts_protocol(obj)\n");
|
||||
}
|
||||
|
||||
// PY-56785
|
||||
public void testTypingSelfNoInspectionReturnSelfMethod() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Builder:\n" +
|
||||
" def foo(self) -> Self:\n" +
|
||||
" result = self.bar()\n" +
|
||||
" return result\n" +
|
||||
"\n" +
|
||||
" def bar(self) -> Self:\n" +
|
||||
" pass\n");
|
||||
}
|
||||
|
||||
// PY-56785
|
||||
public void testTypingSelfClassMethodReturnClsNoHighlighting() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"class Shape:\n" +
|
||||
"\n" +
|
||||
" def __init__(self, scale: float):\n" +
|
||||
" self.scale = None\n" +
|
||||
"\n" +
|
||||
" @classmethod\n" +
|
||||
" def from_config(cls, config: dict[str, float]) -> Self:\n" +
|
||||
" return cls(config[\"scale\"])\n");
|
||||
}
|
||||
|
||||
// PY-56785
|
||||
public void _testTypingSelfAndExplicitClassReturn() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"class SomeClass():\n" +
|
||||
" def foo(self, bar: Self) -> Self:\n" +
|
||||
" return <warning descr=\"Cannot return explicit class in self annotated function\">SomeClass()</warning>\n");
|
||||
}
|
||||
|
||||
// PY-56785
|
||||
public void _testTypingSelfReturnSubClassMethod() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Builder:\n" +
|
||||
" def foo(self) -> Self:\n" +
|
||||
" result = SubBuilder().bar()\n" +
|
||||
" return <warning descr=\"Cannot return explicit class in self annotated function\">result</warning>\n" +
|
||||
"\n" +
|
||||
" def bar(self) -> Self:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class SubBuilder(Builder):\n" +
|
||||
" pass\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,6 +1202,101 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
...""");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testInstanceAndClassChecksOnTypingSelf() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class A:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class B:\n" +
|
||||
" def foo(self: Self):\n" +
|
||||
" assert isinstance(A(), <error descr=\"'Self' cannot be used with instance and class checks\">Self</error>)\n" +
|
||||
" assert issubclass(A, <error descr=\"'Self' cannot be used with instance and class checks\">Self</error>)\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfSubscription() {
|
||||
doTestByText("from typing import Self, Generic, TypeVar\n" +
|
||||
"\n" +
|
||||
"T = TypeVar(\"T\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class A(Generic[T]):\n" +
|
||||
" def foo(self):\n" +
|
||||
" x: Self[<error descr=\"'Self' cannot be parameterized\">int</error>]\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfAnnotationOutsideClass() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"def foo() -> <warning descr=\"Cannot use 'Self' outside class\">Self</warning>:\n" +
|
||||
" pass\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfAnnotationForVariableOutsideClass() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"something: <warning descr=\"Cannot use 'Self' outside class\">Self</warning> | None = None\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfInStaticMethod() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"class SomeClass:\n" +
|
||||
" @staticmethod\n" +
|
||||
" def foo(bar: <warning descr=\"Cannot use 'Self' in staticmethod\">Self</warning>) -> <warning descr=\"Cannot use 'Self' in staticmethod\">Self</warning>:\n" +
|
||||
" return bar\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfParameterHasDifferentAnnotation() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"class SomeClass:\n" +
|
||||
" def foo(self: SomeClass, bar: <warning descr=\"Cannot use 'Self' if 'self' parameter is not 'Self' annotated\">Self</warning>) -> <warning descr=\"Cannot use 'Self' if 'self' parameter is not 'Self' annotated\">Self</warning>:\n" +
|
||||
" return self\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfClsParameterHasDifferentAnnotation() {
|
||||
doTestByText("from __future__ import annotations\n" +
|
||||
"from typing import Self\n" +
|
||||
"\n" +
|
||||
"class SomeClass:\n" +
|
||||
" @classmethod\n" +
|
||||
" def foo(cls: SomeClass, bar: <warning descr=\"Cannot use 'Self' if 'cls' parameter is not 'Self' annotated\">Self</warning>) -> <warning descr=\"Cannot use 'Self' if 'cls' parameter is not 'Self' annotated\">Self</warning>:\n" +
|
||||
" return self\n");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfInStaticMethodBody() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class C:\n" +
|
||||
" @staticmethod\n" +
|
||||
" def m():\n" +
|
||||
" obj: <warning descr=\"Cannot use 'Self' in staticmethod\">Self</warning> = None");
|
||||
}
|
||||
|
||||
// PY-53104
|
||||
public void testTypingSelfInFunctionBodySelfParameterHasDifferentAnnotation() {
|
||||
doTestByText("from typing import Self\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class C:\n" +
|
||||
" def m(self: C):\n" +
|
||||
" obj: <warning descr=\"Cannot use 'Self' if 'self' parameter is not 'Self' annotated\">Self</warning> = None\n");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user