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:
Daniil Kalinin
2023-11-06 19:59:18 +00:00
committed by intellij-monorepo-bot
co-authored by Mikhail Golubev
parent 0ca9ba4a99
commit eb58a3805e
18 changed files with 804 additions and 14 deletions
@@ -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) {
@@ -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;
}
@@ -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;
}
}
@@ -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>)
""")
);
}
}
@@ -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() {