mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-61883 PEP 695 Type Parameter Syntax: Make PyTypingTypeProvider aware of the new-style type parameters and aliases
Inherit PyTypeAliasStatement from PyQualifiedNameOwner to re-use type aliases stack in PyTypingTypeProvider Various tests for the changes above Co-authored-by: Mikhail Golubev <mikhail.golubev@jetbrains.com> GitOrigin-RevId: 242427c6f84c05ec48c94085f20675b8e30f8625
This commit is contained in:
committed by
intellij-monorepo-bot
co-authored by
Mikhail Golubev
parent
0ca9ba4a99
commit
eb58a3805e
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* Represents Type Alias Statement added in <a href="https://peps.python.org/pep-0695/">PEP 695</a>
|
||||
*/
|
||||
public interface PyTypeAliasStatement extends PyStatement, PsiNameIdentifierOwner, PyTypeParameterListOwner, PyTypedElement,
|
||||
StubBasedPsiElement<PyTypeAliasStatementStub>, ScopeOwner {
|
||||
StubBasedPsiElement<PyTypeAliasStatementStub>, PyQualifiedNameOwner, ScopeOwner {
|
||||
|
||||
@Nullable
|
||||
PyExpression getTypeExpression();
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.PROTOCOL_EXT
|
||||
import com.jetbrains.python.psi.AccessDirection
|
||||
import com.jetbrains.python.psi.PyClass
|
||||
import com.jetbrains.python.psi.PyPossibleClassMember
|
||||
import com.jetbrains.python.psi.PyTypeParameter
|
||||
import com.jetbrains.python.psi.PyTypedElement
|
||||
import com.jetbrains.python.psi.impl.PyCallExpressionHelper.resolveImplicitlyInvokedMethods
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
@@ -43,6 +44,9 @@ fun inspectProtocolSubclass(protocol: PyClassType, subclass: PyClassType, contex
|
||||
return@visitMembers true
|
||||
}
|
||||
}
|
||||
if (e is PyTypeParameter) {
|
||||
return@visitMembers true
|
||||
}
|
||||
|
||||
val name = e.name ?: return@visitMembers true
|
||||
when (name) {
|
||||
|
||||
+114
-12
@@ -104,6 +104,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
public static final String ANNOTATED_EXT = "typing_extensions.Annotated";
|
||||
public static final String TYPE_ALIAS = "typing.TypeAlias";
|
||||
public static final String TYPE_ALIAS_EXT = "typing_extensions.TypeAlias";
|
||||
public static final String TYPE_ALIAS_TYPE = "typing.TypeAliasType";
|
||||
private static final String SPECIAL_FORM = "typing._SpecialForm";
|
||||
private static final String SPECIAL_FORM_EXT = "typing_extensions._SpecialForm";
|
||||
public static final String REQUIRED = "typing.Required";
|
||||
@@ -663,6 +664,13 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
if (!isGeneric(cls, context.getTypeContext())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (cls.getTypeParameterList() != null) {
|
||||
List<PyTypeParameter> typeParameters = cls.getTypeParameterList().getTypeParameters();
|
||||
return StreamEx.of(typeParameters)
|
||||
.map(typeParameter -> getTypeParameterTypeFromTypeParameter(typeParameter, context))
|
||||
.nonNull()
|
||||
.toList();
|
||||
}
|
||||
// See https://mypy.readthedocs.io/en/stable/generics.html#defining-sub-classes-of-generic-classes
|
||||
List<PySubscriptionExpression> parameterizedSuperClassExpressions =
|
||||
ContainerUtil.filterIsInstance(PyClassElementType.getSuperClassExpressions(cls), PySubscriptionExpression.class);
|
||||
@@ -694,9 +702,19 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
}
|
||||
|
||||
public static boolean isGeneric(@NotNull PyWithAncestors descendant, @NotNull TypeEvalContext context) {
|
||||
if (descendant instanceof PyClass pyClass && pyClass.getTypeParameterList() != null ||
|
||||
descendant instanceof PyClassType pyClassType && pyClassType.getPyClass().getTypeParameterList() != null) {
|
||||
return true;
|
||||
}
|
||||
for (PyClassLikeType ancestor : descendant.getAncestorTypes(context)) {
|
||||
if (ancestor != null && GENERIC_CLASSES.contains(ancestor.getClassQName())) {
|
||||
return true;
|
||||
if (ancestor != null) {
|
||||
if (GENERIC_CLASSES.contains(ancestor.getClassQName())) {
|
||||
return true;
|
||||
}
|
||||
else if (ancestor instanceof PyClassType classType &&
|
||||
classType.getPyClass().getTypeParameterList() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -711,7 +729,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
private 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())) {
|
||||
for (Pair<PyQualifiedNameOwner, PsiElement> pair : tryResolvingWithAliases(expression, context.getTypeContext())) {
|
||||
final Ref<PyType> typeRef = getTypeForResolvedElement(expression, pair.getFirst(), pair.getSecond(), context);
|
||||
if (typeRef != null) {
|
||||
final PyType type = typeRef.get();
|
||||
@@ -777,7 +795,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
|
||||
@Nullable
|
||||
private static Ref<PyType> getTypeForResolvedElement(@NotNull PyExpression typeHint,
|
||||
@Nullable PyTargetExpression alias,
|
||||
@Nullable PyQualifiedNameOwner alias,
|
||||
@NotNull PsiElement resolved,
|
||||
@NotNull Context context) {
|
||||
if (alias != null) {
|
||||
@@ -856,6 +874,10 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
if (unpackedType != null) {
|
||||
return Ref.create(unpackedType);
|
||||
}
|
||||
final PyType typeParameterType = getTypeParameterTypeFromTypeParameter(resolved, context);
|
||||
if (typeParameterType != null) {
|
||||
return Ref.create(typeParameterType);
|
||||
}
|
||||
final PyType paramSpecType = getParamSpecType(resolved, context);
|
||||
if (paramSpecType != null) {
|
||||
return Ref.create(anchorTypeParameter(typeHint, paramSpecType, context));
|
||||
@@ -958,7 +980,9 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
|
||||
@Nullable
|
||||
private static PyType anchorTypeParameter(@NotNull PyExpression typeHint, @Nullable PyType type, @NotNull Context context) {
|
||||
PyTargetExpression targetExpr = context.getTypeAliasStack().isEmpty() ? null : context.getTypeAliasStack().peek();
|
||||
PyQualifiedNameOwner typeParamDefinitionFromStack = context.getTypeAliasStack().isEmpty() ? null : context.getTypeAliasStack().peek();
|
||||
assert typeParamDefinitionFromStack == null || typeParamDefinitionFromStack instanceof PyTargetExpression;
|
||||
PyTargetExpression targetExpr = (PyTargetExpression)typeParamDefinitionFromStack;
|
||||
if (type instanceof PyTypeVarTypeImpl typeVar) {
|
||||
return typeVar.withScopeOwner(getTypeParameterScope(typeVar.getName(), typeHint, context)).withTargetExpression(targetExpr);
|
||||
}
|
||||
@@ -1490,6 +1514,35 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyTypeParameterType getTypeParameterTypeFromTypeParameter(@NotNull PsiElement element, @NotNull Context context) {
|
||||
if (element instanceof PyTypeParameter typeParameter) {
|
||||
|
||||
PyTypeParameterListOwner typeParameterOwner = PsiTreeUtil.getStubOrPsiParentOfType(element, PyTypeParameterListOwner.class);
|
||||
PyQualifiedNameOwner scopeOwner = typeParameterOwner instanceof PyQualifiedNameOwner qualifiedNameOwner ? qualifiedNameOwner : null;
|
||||
|
||||
String boundExpressionText = typeParameter.getBoundExpressionText();
|
||||
String name = typeParameter.getName();
|
||||
PyTypeParameter.Kind kind = typeParameter.getKind();
|
||||
|
||||
PyExpression boundExpression = boundExpressionText != null
|
||||
? PyUtil.createExpressionFromFragment(boundExpressionText, typeParameter.getContainingFile())
|
||||
: null;
|
||||
|
||||
if (name != null) {
|
||||
return switch (kind) {
|
||||
case TypeVar -> {
|
||||
PyType boundType = boundExpression != null ? getTypeParameterBoundType(boundExpression, context) : null;
|
||||
yield new PyTypeVarTypeImpl(name, boundType).withScopeOwner(scopeOwner);
|
||||
}
|
||||
case ParamSpec -> new PyParamSpecType(name).withScopeOwner(scopeOwner);
|
||||
case TypeVarTuple -> new PyTypeVarTupleTypeImpl(name).withScopeOwner(scopeOwner);
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// See https://peps.python.org/pep-0484/#scoping-rules-for-type-variables
|
||||
@Nullable
|
||||
private static PyQualifiedNameOwner getTypeParameterScope(@NotNull String name, @NotNull PyExpression typeHint, @NotNull Context context) {
|
||||
@@ -1521,7 +1574,8 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
// While evaluating type hints of enclosing functions' parameters, resolving to the same TypeVar
|
||||
// definition shouldn't trigger the protection against recursive aliases, so we manually remove
|
||||
// it from the top for the time being.
|
||||
PyTargetExpression typeVarDeclaration = context.getTypeAliasStack().pop();
|
||||
PyQualifiedNameOwner typeVarDeclaration = context.getTypeAliasStack().pop();
|
||||
assert typeVarDeclaration instanceof PyTargetExpression;
|
||||
try {
|
||||
if (owner instanceof PyClass) {
|
||||
return StreamEx.of(collectTypeParameters((PyClass)owner, context))
|
||||
@@ -1625,6 +1679,22 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
return PyUnionType.union(types);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType getTypeParameterBoundType(@NotNull PyExpression boundExpression, @NotNull Context context) {
|
||||
PyExpression bound = PyPsiUtils.flattenParens(boundExpression);
|
||||
if (bound != null) {
|
||||
if (bound instanceof PyTupleExpression tupleExpression) {
|
||||
return StreamEx.of(tupleExpression.getElements())
|
||||
.map(expr -> Ref.deref(getType(expr, context)))
|
||||
.collect(PyTypeUtil.toUnion());
|
||||
}
|
||||
else {
|
||||
return Ref.deref(getType(bound, context));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyType> getIndexTypes(@NotNull PySubscriptionExpression expression, @NotNull Context context) {
|
||||
final List<PyType> types = new ArrayList<>();
|
||||
@@ -1685,9 +1755,9 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<Pair<PyTargetExpression, PsiElement>> tryResolvingWithAliases(@NotNull PyExpression expression,
|
||||
private static List<Pair<PyQualifiedNameOwner, PsiElement>> tryResolvingWithAliases(@NotNull PyExpression expression,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final List<Pair<PyTargetExpression, PsiElement>> elements = new ArrayList<>();
|
||||
final List<Pair<PyQualifiedNameOwner, PsiElement>> elements = new ArrayList<>();
|
||||
if (expression instanceof PyReferenceExpression) {
|
||||
final List<PsiElement> results;
|
||||
if (context.maySwitchToAST(expression)) {
|
||||
@@ -1722,6 +1792,20 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (element instanceof PyTypeAliasStatement typeAliasStatement) {
|
||||
PyExpression assignedValue;
|
||||
if (context.maySwitchToAST(typeAliasStatement)) {
|
||||
assignedValue = typeAliasStatement.getTypeExpression();
|
||||
}
|
||||
else {
|
||||
String assignedTypeText = typeAliasStatement.getTypeExpressionText();
|
||||
assignedValue = assignedTypeText != null ? toExpression(assignedTypeText, typeAliasStatement) : null;
|
||||
}
|
||||
if (assignedValue != null) {
|
||||
elements.add(Pair.create(typeAliasStatement, assignedValue));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (element != null) {
|
||||
elements.add(Pair.create(null, element));
|
||||
}
|
||||
@@ -1737,8 +1821,26 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
final QualifiedName qualifiedName = expression.asQualifiedName();
|
||||
final PyFile pyFile = as(FileContextUtil.getContextFile(expression), PyFile.class);
|
||||
|
||||
if (pyFile != null && qualifiedName != null) {
|
||||
return PyResolveUtil.resolveQualifiedNameInScope(qualifiedName, pyFile, context);
|
||||
PsiElement anchor = expression.getContainingFile().getUserData(FRAGMENT_OWNER);
|
||||
ScopeOwner scopeOwner;
|
||||
|
||||
if (anchor == null) {
|
||||
scopeOwner = pyFile;
|
||||
}
|
||||
else if (anchor instanceof ScopeOwner anchorAsScope) {
|
||||
scopeOwner = anchorAsScope;
|
||||
}
|
||||
else {
|
||||
scopeOwner = ScopeUtil.getScopeOwner(anchor);
|
||||
}
|
||||
|
||||
if (scopeOwner != null && qualifiedName != null) {
|
||||
List<PsiElement> results = new ArrayList<>();
|
||||
while (scopeOwner != null) {
|
||||
results.addAll(PyResolveUtil.resolveQualifiedNameInScope(qualifiedName, scopeOwner, context));
|
||||
scopeOwner = ScopeUtil.getScopeOwner(scopeOwner);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
return Collections.singletonList(expression);
|
||||
}
|
||||
@@ -1931,7 +2033,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
|
||||
static class Context {
|
||||
@NotNull private final TypeEvalContext myContext;
|
||||
@NotNull private final Stack<PyTargetExpression> myTypeAliasStack = new Stack<>();
|
||||
@NotNull private final Stack<PyQualifiedNameOwner> myTypeAliasStack = new Stack<>();
|
||||
|
||||
private Context(@NotNull TypeEvalContext context) {
|
||||
myContext = context;
|
||||
@@ -1943,7 +2045,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Stack<PyTargetExpression> getTypeAliasStack() {
|
||||
public Stack<PyQualifiedNameOwner> getTypeAliasStack() {
|
||||
return myTypeAliasStack;
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -8,8 +8,11 @@ import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyStubElementTypes;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
|
||||
import com.jetbrains.python.psi.stubs.PyTypeAliasStatementStub;
|
||||
import com.jetbrains.python.psi.types.PyClassTypeImpl;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -101,7 +104,11 @@ public class PyTypeAliasStatementImpl extends PyBaseElementImpl<PyTypeAliasState
|
||||
@Override
|
||||
@Nullable
|
||||
public PyType getType(@NotNull TypeEvalContext context, TypeEvalContext.@NotNull Key key) {
|
||||
// TODO
|
||||
PyPsiFacade facade = PyPsiFacade.getInstance(this.getProject());
|
||||
PyClass typeAliasTypeClass = facade.createClassByQName(PyTypingTypeProvider.TYPE_ALIAS_TYPE, this);
|
||||
if (typeAliasTypeClass != null) {
|
||||
return new PyClassTypeImpl(typeAliasTypeClass, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -116,4 +123,9 @@ public class PyTypeAliasStatementImpl extends PyBaseElementImpl<PyTypeAliasState
|
||||
super.subtreeChanged();
|
||||
ControlFlowCache.clear(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getQualifiedName() {
|
||||
return QualifiedNameFinder.getQualifiedName(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,16 @@ public class PyTypeParameterImpl extends PyBaseElementImpl<PyTypeParameterStub>
|
||||
@Override
|
||||
@Nullable
|
||||
public PyType getType(@NotNull TypeEvalContext context, TypeEvalContext.@NotNull Key key) {
|
||||
PyPsiFacade facade = PyPsiFacade.getInstance(this.getProject());
|
||||
Kind kind = this.getKind();
|
||||
PyClass pyClass = switch (kind) {
|
||||
case TypeVar -> facade.createClassByQName(PyTypingTypeProvider.TYPE_VAR, this);
|
||||
case TypeVarTuple -> facade.createClassByQName(PyTypingTypeProvider.TYPE_VAR_TUPLE, this);
|
||||
case ParamSpec -> facade.createClassByQName(PyTypingTypeProvider.TYPING_PARAM_SPEC, this);
|
||||
};
|
||||
if (pyClass != null) {
|
||||
return new PyClassTypeImpl(pyClass, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
from typing import Protocol
|
||||
|
||||
# classes having type parameter list implicitly inherit from typing.Generic
|
||||
class Clazz[T](Protocol): ...
|
||||
@@ -0,0 +1,3 @@
|
||||
class Stack[T]:
|
||||
def pop() -> T:
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo[T](x: T) -> T:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
type alias[T, U] = dict[T, U]
|
||||
@@ -0,0 +1,4 @@
|
||||
from typing import Callable
|
||||
|
||||
def changes_return_type_to_str[**P](x: Callable[P, int]) -> Callable[P, str]:
|
||||
...
|
||||
@@ -0,0 +1,3 @@
|
||||
from typing import List, Union
|
||||
|
||||
type MyType = Union[List['MyType'], int]
|
||||
@@ -0,0 +1,2 @@
|
||||
type alias2 = 'alias'
|
||||
type alias = alias2
|
||||
@@ -2189,6 +2189,76 @@ public class Py3TypeTest extends PyTestCase {
|
||||
pass
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testParamSpecExampleWithPEP695Syntax() {
|
||||
doTest("(a: str, b: bool) -> str",
|
||||
"""
|
||||
from typing import Callable
|
||||
|
||||
def changes_return_type_to_str[**P](x: Callable[P, int]) -> Callable[P, str]: ...
|
||||
|
||||
def returns_int(a: str, b: bool) -> int:
|
||||
return 42
|
||||
|
||||
expr = changes_return_type_to_str(returns_int)""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testParamSpecInImportedFileWithPEP695Syntax() {
|
||||
doMultiFileTest("(a: str, b: bool) -> str",
|
||||
"""
|
||||
from a import changes_return_type_to_str
|
||||
|
||||
def returns_int(a: str, b: bool) -> int:
|
||||
return 42
|
||||
|
||||
expr = changes_return_type_to_str(returns_int)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testParamSpecConcatenateTransformWithPEP695Syntax() {
|
||||
doTest("(str, args: tuple[bool, ...]) -> bool",
|
||||
"""
|
||||
from typing import Callable, Concatenate
|
||||
|
||||
def bar(x: int, *args: bool) -> int: ...
|
||||
|
||||
|
||||
def transform[**P](
|
||||
x: Callable[Concatenate[int, P], int]
|
||||
) -> Callable[Concatenate[str, P], bool]:
|
||||
def inner(s: str, *args: P.args):
|
||||
return True
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
expr = transform(bar)""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testParamSpecUserGenericClassWithPEP695Syntax() {
|
||||
doTest("Y[int, [int, str, bool]]",
|
||||
"""
|
||||
from typing import Callable
|
||||
|
||||
class Y[U, **P]:
|
||||
f: Callable[P, str]
|
||||
attr: U
|
||||
|
||||
def __init__(self, f: Callable[P, str], attr: U) -> None:
|
||||
self.f = f
|
||||
self.attr = attr
|
||||
|
||||
|
||||
def a(q: int, p: str, r: bool) -> str: ...
|
||||
|
||||
|
||||
expr = Y(a, 1)
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
private void doTest(final String expectedType, final String text) {
|
||||
|
||||
@@ -420,6 +420,78 @@ public class PyTypeParameterScopingTest extends PyTestCase {
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testFunctionDeclaresOwnTypeVarWithPEP695Syntax() {
|
||||
assertTypeParameterOwner("func", "func",
|
||||
"""
|
||||
def func[T](x) -> T:
|
||||
pass
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testMethodUsesTypeVarOfItsClassInAnnotationWithPEP695Syntax() {
|
||||
assertTypeParameterOwner("C.m", "C",
|
||||
"""
|
||||
class C[T]:
|
||||
def m(self, x: T) -> T:
|
||||
pass
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testMethodDeclaresOwnTypeVarWithPEP695Syntax() {
|
||||
assertTypeParameterOwner("C.m", "C.m",
|
||||
"""
|
||||
class C[T]:
|
||||
def m[V](self, x: V) -> V:
|
||||
pass
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testInstanceAttributeUsesTypeVarOfItsClassWithPEP695Syntax() {
|
||||
assertTypeParameterOwner("C.attr", "C",
|
||||
"""
|
||||
class C[T]:
|
||||
def __init__(self):
|
||||
self.attr: T = ...
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testClassDeclaresOwnParamSpecWithPEP695Syntax() {
|
||||
assertTypeParameterOwner("C", "C",
|
||||
"""
|
||||
class C[**P]:
|
||||
...
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testFunctionUsesTypeVarOfEnclosingFunctionWithPEP695Syntax() {
|
||||
assertTypeParameterOwner("dec.g", "dec",
|
||||
"""
|
||||
def dec[T](x: T):
|
||||
def g(func) -> T:
|
||||
...
|
||||
return g
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testFunctionUsesTypeVarOfEnclosingMethodClassWithPEP695Syntax() {
|
||||
assertTypeParameterOwner("C.method.f", "C",
|
||||
"""
|
||||
class C[T]:
|
||||
def method(self, x: T) -> None:
|
||||
def f() -> T:
|
||||
...
|
||||
""");
|
||||
}
|
||||
|
||||
private void assertTypeParameterOwner(@NotNull String elementQName, @NotNull String scopeOwnerQName, @NotNull String text) {
|
||||
VirtualFile virtualFile;
|
||||
try {
|
||||
|
||||
@@ -3925,6 +3925,461 @@ public class PyTypingTest extends PyTestCase {
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testSimpleGenericClassWithPEP695TypeParameterSyntax() {
|
||||
doTest("str", """
|
||||
class MyStack[T]:
|
||||
def pop(self) -> T:
|
||||
pass
|
||||
|
||||
stack = MyStack[str]()
|
||||
expr = stack.pop()
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testSimpleGenericFunWithPEP695Syntax() {
|
||||
doTest("int", """
|
||||
def foo[T](x: T) -> T:
|
||||
return x
|
||||
|
||||
expr = foo(1)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericClassParameterizedWithTypeOfConstructorArgumentWithPEP695Syntax() {
|
||||
doTest("C[int]", """
|
||||
class C[T]:
|
||||
def __init__(self, x: T):
|
||||
pass
|
||||
|
||||
expr = C(10)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericBaseWithPEP695SyntaxClassSpecifiedThroughAlias() {
|
||||
doTest("int",
|
||||
"""
|
||||
class Super[T]:
|
||||
pass
|
||||
|
||||
Alias = Super
|
||||
|
||||
class Sub[T](Alias[T]):
|
||||
pass
|
||||
|
||||
def f[T](x: Super[T]) -> T:
|
||||
pass
|
||||
|
||||
arg: Sub[int]
|
||||
expr = f(arg)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testExplicitlyParametrizedGenericClassInstanceWithPEP695Syntax() {
|
||||
doTest("Node[int]",
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
class Node[T]:
|
||||
def __init__(self, children: List[T]):
|
||||
self.children = children
|
||||
|
||||
|
||||
expr = Node[int]()""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testParameterizedWithPEP695SyntaxClassInheritance() {
|
||||
doTest("int",
|
||||
"""
|
||||
class B[T]:
|
||||
def foo(self) -> T:
|
||||
pass
|
||||
|
||||
class C[T](B[T]):
|
||||
def __init__(self, x: T):
|
||||
pass
|
||||
|
||||
expr = C(10).foo()
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericFieldOfClassParameterizedWithNewPEP695Syntax() {
|
||||
doTest("str",
|
||||
"""
|
||||
class C[T]:
|
||||
def __init__(self, foo: T):
|
||||
self.foo = foo
|
||||
|
||||
def f() -> C[str]:
|
||||
return C('test')
|
||||
|
||||
x = f()
|
||||
expr = x.foo
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testMultiTypeExplicitlyParametrizedGenericClassInstanceWithPEP695Syntax() {
|
||||
doTest("float",
|
||||
"""
|
||||
class FirstType[T]: pass
|
||||
class SecondType[V]: pass
|
||||
class ThirdType[Z]: pass
|
||||
class Clazz[T, V, Z](FirstType[T], SecondType[V], ThirdType[Z]):
|
||||
first: T
|
||||
second: V
|
||||
third: Z
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
node = Clazz[str, int, float]()
|
||||
expr = node.third""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericUserFunctionWithManyParamsAndNestedCallWithPEP695Syntax() {
|
||||
doTest("tuple[bool, int, LiteralString]",
|
||||
"""
|
||||
def myid[T](x: T) -> T:
|
||||
pass
|
||||
|
||||
def f[T, U, V](x: T, y: U, z: V):
|
||||
return myid(x), myid(y), myid(z)
|
||||
|
||||
expr = f(True, 1, 'foo')
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericSubstitutionInDeepHierarchyWithPEP695Syntax() {
|
||||
doTest("int",
|
||||
"""
|
||||
class Root[T1, T2]:
|
||||
def m(self) -> T2:
|
||||
pass
|
||||
|
||||
class Base3[T1](Root[T1, int]):
|
||||
pass
|
||||
|
||||
class Base2[T1](Base3[T1]):
|
||||
pass
|
||||
|
||||
class Base1[T1](Base2[T1]):
|
||||
pass
|
||||
|
||||
class Sub[T1](Base1[T1]):
|
||||
pass
|
||||
|
||||
expr = Sub().m()
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericClassSpecializesInheritedParameterAndAddsNewOneWithPEP695Syntax() {
|
||||
doTest("StrBoxWithExtra[int]",
|
||||
"""
|
||||
class Box[T]:
|
||||
pass
|
||||
|
||||
class StrBoxWithExtra[T2](Box[str]):
|
||||
def __init__(self, extra: T2):
|
||||
self.extra = extra
|
||||
|
||||
expr = StrBoxWithExtra(42)""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testSimpleTypeAliasWithPEP695Syntax() {
|
||||
doTest("str",
|
||||
"""
|
||||
type myType = str
|
||||
def foo() -> myType:
|
||||
pass
|
||||
expr = foo()
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericTypeAliasForTupleWithPEP695Syntax() {
|
||||
doTest("tuple[int, int]",
|
||||
"""
|
||||
type Pair[T] = tuple[T, T]
|
||||
expr: Pair[int]""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericTypeAliasParameterizedInTwoStepsWithPEP695Syntax() {
|
||||
doTest("dict[int, str]",
|
||||
"""
|
||||
type Alias1[T1, T2] = dict[T1, T2]
|
||||
type Alias2[T2] = Alias1[int, T2]
|
||||
expr: Alias2[str]""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericClassDefinedInAnotherFileWithPEP695Syntax() {
|
||||
doMultiFileStubAwareTest("int",
|
||||
"""
|
||||
from a import Stack
|
||||
|
||||
expr = Stack[int]().pop()""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testRecursiveTypeAliasInAnotherFilePEP695Syntax() {
|
||||
doMultiFileStubAwareTest("list | int",
|
||||
"""
|
||||
from a import MyType
|
||||
|
||||
expr: MyType = ...""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testTrivialRecursiveTypeAliasInAnotherFileWithPEP695Syntax() {
|
||||
doMultiFileStubAwareTest("Any",
|
||||
"""
|
||||
from a import alias
|
||||
|
||||
expr: alias""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericTypeAliasInAnotherFileWithPEP695Syntax() {
|
||||
doMultiFileStubAwareTest("dict[str, int]",
|
||||
"""
|
||||
from a import alias
|
||||
|
||||
expr: alias[str, int]""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericFunctionInAnotherFileWithPEP695Syntax() {
|
||||
doMultiFileStubAwareTest("int",
|
||||
"""
|
||||
from a import foo
|
||||
|
||||
expr = foo(42)""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericProtocolUnificationSameTypeVarWithPEP695Syntax() {
|
||||
doTest("list[int]",
|
||||
"""
|
||||
from typing import Protocol
|
||||
|
||||
class SupportsIter[T](Protocol):
|
||||
def __iter__(self) -> T:
|
||||
pass
|
||||
|
||||
|
||||
def my_iter[T](x: SupportsIter[T]) -> T:
|
||||
pass
|
||||
|
||||
|
||||
class MyList:
|
||||
def __iter__(self) -> list[int]:
|
||||
pass
|
||||
|
||||
|
||||
expr = my_iter(MyList())""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericProtocolUnificationSeparateTypeVarWithPEP695Syntax() {
|
||||
doTest("list[int]",
|
||||
"""
|
||||
from typing import Protocol
|
||||
|
||||
class SupportsIter[T](Protocol):
|
||||
def __iter__(self) -> T:
|
||||
pass
|
||||
|
||||
def my_iter[T2](x: SupportsIter[T2]) -> T2:
|
||||
pass
|
||||
|
||||
class MyList:
|
||||
def __iter__(self) -> list[int]:
|
||||
pass
|
||||
|
||||
expr = my_iter(MyList())""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericProtocolUnificationGenericImplementationWithGenericSuperclassWithPEP695Syntax() {
|
||||
doTest("int",
|
||||
"""
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class Fooable[T1](Protocol):
|
||||
def foo(self) -> T1:
|
||||
...
|
||||
|
||||
class Super[T2]:
|
||||
def foo(self) -> T2:
|
||||
...
|
||||
|
||||
class MyClass[T2](Super[T2]):
|
||||
pass
|
||||
|
||||
def f[T1](x: Fooable[T1]) -> T1:
|
||||
...
|
||||
|
||||
obj: MyClass[int]
|
||||
expr = f(obj)""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericVariadicByCallableWithPEP695Syntax() {
|
||||
doTest("tuple[int, str]",
|
||||
"""
|
||||
from typing import Callable, Tuple
|
||||
|
||||
def foo[*Ts](f: Callable[[*Ts], Tuple[*Ts]]) -> Tuple[*Ts]: ...
|
||||
|
||||
def bar(a: int, b: str) -> Tuple[int, str]: ...
|
||||
|
||||
expr = foo(bar)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericVariadicByCallablePrefixSuffixWithPEP695Syntax() {
|
||||
doTest("tuple[str, str, float, int, bool]",
|
||||
"""
|
||||
from typing import Callable, Tuple
|
||||
|
||||
def foo[T, *Ts](f: Callable[[int, *Ts, T], Tuple[T, *Ts]]) -> Tuple[str, *Ts, int, T]: ...
|
||||
|
||||
def bar(a: int, b: str, c: float, d: bool) -> Tuple[bool, str, float]: ...
|
||||
|
||||
expr = foo(bar)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericVariadicClassWithPEP695Syntax() {
|
||||
doTest("A[float, bool, list[LiteralString]]",
|
||||
"""
|
||||
from typing import Generic, Tuple
|
||||
|
||||
class A[*Ts]:
|
||||
def __init__(self, value: Tuple[int, *Ts]) -> None:
|
||||
self.field: Tuple[int, *Ts] = value
|
||||
|
||||
tpl = (42, 1.1, True, ['42'])
|
||||
expr = A(tpl)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericVariadicClassFieldWithPEP695Syntax() {
|
||||
doTest("tuple[int, float, bool, list[LiteralString]]",
|
||||
"""
|
||||
from typing import Tuple
|
||||
|
||||
class A[*Ts]:
|
||||
def __init__(self, value: Tuple[int, *Ts]) -> None:
|
||||
self.field: Tuple[int, *Ts] = value
|
||||
|
||||
tpl = (42, 1.1, True, ['42'])
|
||||
a = A(tpl)
|
||||
expr = a.field
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericVariadicAndGenericClassWithPEP695Syntax() {
|
||||
doTest("A[int | str, int | str, list[int]]",
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class A[T, *Ts, T1]:
|
||||
def __init__(self, t: T, tpl: Tuple[*Ts], t1: T1) -> None:
|
||||
...
|
||||
|
||||
x: int | str
|
||||
expr = A(x, (x,), [1])
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericVariadicClassMethodAddAxisPrefixWithPEP695Syntax() {
|
||||
doTest("Array[LiteralString, int, bool]",
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Tuple, NewType
|
||||
|
||||
class Array[*Shape]:
|
||||
def __init__(self, shape: Tuple[*Shape]):
|
||||
self._shape: Tuple[*Shape] = shape
|
||||
|
||||
def add_axis_prefix[T](self, t: T) -> Array[T, *Shape]: ...
|
||||
|
||||
shape = (42, True)
|
||||
arr: Array[int, bool] = Array(shape)
|
||||
expr = arr.add_axis_prefix('')
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testGenericVariadicStarArgsAndTypeVarsWithPEP695Syntax() {
|
||||
doTest("tuple[LiteralString, list[int], bool, int]",
|
||||
"""
|
||||
from typing import TypeVarTuple, Tuple, TypeVar
|
||||
|
||||
def args_to_tuple[T1, T2, *Ts](t1: T1, t2: T2, *args: *Tuple[T2, *Ts, float]) -> Tuple[T2, *Ts, T1]: ...
|
||||
|
||||
expr = args_to_tuple(1, 'a', 'a', [1], True, 3.3)
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testTypeAliasStatementTypeNotInterpretedAsAssignedTypeOutsideOfTypeHint() {
|
||||
doTest("Any",
|
||||
"""
|
||||
type myType = str
|
||||
expr = myType
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testTypeParameterTypeIsTypingTypeVar() {
|
||||
doTest("TypeVar",
|
||||
"""
|
||||
def foo[T]():
|
||||
expr = T
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testTypeParameterTypeIsTypingParamSpec() {
|
||||
doTest("ParamSpec",
|
||||
"""
|
||||
def foo[**P]():
|
||||
expr = P
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testTypeParameterTypeIsTypingTypeVarTuple() {
|
||||
doTest("TypeVarTuple",
|
||||
"""
|
||||
def foo[*Ts]():
|
||||
expr = Ts
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
private void doTestNoInjectedText(@NotNull String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());
|
||||
|
||||
@@ -37,6 +37,13 @@ public class PyProtocolInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-61857
|
||||
public void testClassWithTypeParameterListNotReported() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
|
||||
super.doTest();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTest() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON37, () -> super.doTest());
|
||||
|
||||
@@ -1531,4 +1531,30 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
""")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testTypeParameterBoundWithPEP695Syntax() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON312,
|
||||
() -> doTestByText("""
|
||||
def foo[T: str](p: T):
|
||||
return p
|
||||
|
||||
expr = foo(<warning descr="Expected type 'T', got 'int' instead">42</warning>)
|
||||
""")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-61883
|
||||
public void testTypeParameterConstraintsWithPEP695Syntax() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON312,
|
||||
() -> doTestByText("""
|
||||
def foo[T: (str, bool)](p: T):
|
||||
return p
|
||||
|
||||
expr = foo(<warning descr="Expected type 'T', got 'int' instead">42</warning>)
|
||||
""")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -915,6 +915,19 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON310, this::doTest);
|
||||
}
|
||||
|
||||
// PY-63361
|
||||
public void testParametrizationOfClassWithTypeParameterListIsNotReported() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
|
||||
doTestByText("""
|
||||
class MyGeneric[T]:
|
||||
pass
|
||||
|
||||
class Sub(MyGeneric[int]):
|
||||
pass
|
||||
""");
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user