PY-62608 PEP 695 Type Parameter Syntax: Resolve and scoping for type parameters in type aliases

GitOrigin-RevId: be532456e7a9c470d5d0a2770a7a2eb9b9b6e8de
This commit is contained in:
Daniil Kalinin
2023-09-27 12:56:13 +02:00
committed by intellij-monorepo-bot
parent 65cc3ddfd0
commit 97185d17c8
29 changed files with 407 additions and 12 deletions

View File

@@ -1697,4 +1697,168 @@ public abstract class PyCommonResolveTest extends PyCommonResolveTestCase {
public void testNumpyDocstringAttributeNameResolvesToInheritedClassAttribute() {
runWithDocStringFormat(DocStringFormat.NUMPY, () -> assertResolvesTo(PyTargetExpression.class, "bar"));
}
// PY-61878
public void testTypeAliasStatement() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeAliasStatement.class, "myType");
});
}
// PY-61878
public void testTypeParameterResolvedInsideTypeAliasStatement() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideNamedParameterInFunctionDeclaration() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideNestedFunction() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testFunctionParameterDefaultValueNotResolvedToTypeParameterInFunctionDeclaration() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTargetExpression.class, "T");
});
}
// PY-61877
public void testDecoratorArgumentNotResolvedToTypeParameterOfDecoratedFunction() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertNotResolved();
});
}
// PY-61877
public void testTypeParameterResolvedInsideReturnTypeInFunctionDeclaration() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testNotResolvedToTypeParameterOutsideOfFunctionScope() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertNotResolved();
});
}
// PY-61877
public void testNotResolvedToTypeParameterOutsideOfClassScope() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertNotResolved();
});
}
// PY-61878
public void testNotResolvedToTypeParameterOutsideOfTypeAliasStatement() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTargetExpression.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideClassDeclaration() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideVariableAnnotationInsideFunction() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideClassAttributeAnnotation() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideNamedParameterOfClassMethod() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideReturnTypeAnnotationOfClassMethod() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testVariableInsideFunctionResolvedToTypeParameterInsteadOfGlobalVariableWithTheSameName() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testTypeParameterResolvedInsideNestedClass() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testGlobalVariableRedeclarationWithSameAsTypeParameterNameInsideNestedClassNotResolvedToTypeParameter() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTargetExpression.class, "T");
});
}
// PY-61877
public void testReferenceInFunctionInsideNestedClassResolvedToTypeParameterIfTheOuterClassIsParameterized() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTypeParameter.class, "T");
});
}
// PY-61877
public void testReferenceInFunctionInsideNestedClassResolvedToGlobalVariableIfTheOuterClassIsNotParameterized() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTargetExpression.class, "T");
});
}
// PY-61877
public void testReferenceInsideNestedFunctionNotResolvedToTypeParameterOfOuterClass() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
assertResolvesTo(PyTargetExpression.class, "T");
});
}
// [TODO] daniil.kalinin enable when resolve for collisions in type parameter names and class attribute names is implemented
// PY-61877
//public void testClassAttributeDeclarationWithSameAsTypeParameterNameNotResolvedToTypeParameter() {
// runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
// assertResolvesTo(PyTargetExpression.class, "T");
// });
//}
// [TODO] daniil.kalinin enable when resolve for collisions in type parameter names and class attribute names is implemented
// PY-61877
//public void testGlobalVariableRedeclarationWithSameAsTypeParameterNameInsideClassNotResolvedToTypeParameter() {
// runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {
// assertResolvesTo(PyTargetExpression.class, "T");
// });
//}
}

View File

@@ -56,6 +56,17 @@ public abstract class PyCommonResolveTestCase extends PythonCommonTestCase {
return assertResolvesTo(aClass, name, null);
}
protected void assertNotResolved() {
final PsiElement element;
try {
element = doResolve();
}
catch (Exception e) {
throw new RuntimeException(e);
}
assertNull(element);
}
protected <T extends PsiElement> T assertResolvesTo(final Class<T> aClass,
final String name,
String containingFilePath) {

View File

@@ -3,6 +3,7 @@ package com.jetbrains.python.psi;
import com.intellij.psi.PsiNameIdentifierOwner;
import com.intellij.psi.StubBasedPsiElement;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.psi.stubs.PyTypeAliasStatementStub;
import org.jetbrains.annotations.Nullable;
@@ -10,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> {
StubBasedPsiElement<PyTypeAliasStatementStub>, ScopeOwner {
@Nullable
PyExpression getTypeExpression();

View File

@@ -113,24 +113,32 @@ public final class ScopeUtil {
if (decoratorAncestor != null && !isAncestor(decoratorAncestor, firstOwner, true)) {
return nextOwner;
}
// References in default values or in annotations of parameters are resolved outside of the function (if the lambda is not inside the
// default value)
/*
* References in default values are resolved outside of the function (if the lambda is not inside the default value).
* Annotations of parameters are resolved outside of the function if the function doesn't have type parameters list
*/
final PyNamedParameter parameterAncestor = getParentOfType(element, PyNamedParameter.class);
if (parameterAncestor != null && !isAncestor(parameterAncestor, firstOwner, true)) {
final PyExpression defaultValue = parameterAncestor.getDefaultValue();
final PyAnnotation annotation = parameterAncestor.getAnnotation();
if (isAncestor(defaultValue, element, false) || isAncestor(annotation, element, false)) {
return nextOwner;
if (firstOwner instanceof PyFunction function) {
PyTypeParameterList typeParameterList = function.getTypeParameterList();
if ((typeParameterList == null && isAncestor(annotation, element, false))
|| (isAncestor(defaultValue, element, false))) {
return nextOwner;
}
}
}
// Superclasses are resolved outside of the class
// Superclasses are resolved outside of the class if the class doesn't have type parameters list
final PyClass containingClass = getParentOfType(element, PyClass.class);
if (containingClass != null && isAncestor(containingClass.getSuperClassExpressionList(), element, false)) {
if (containingClass != null && isAncestor(containingClass.getSuperClassExpressionList(), element, false) && containingClass.getTypeParameterList() == null) {
return nextOwner;
}
// Function return annotations and type comments are resolved outside of the function
// Function return annotations and type comments are resolved outside of the function if the function doesn't have type parameters list
if (firstOwner instanceof PyFunction function) {
if (isAncestor(function.getAnnotation(), element, false) || isAncestor(function.getTypeComment(), element, false)) {
PyTypeParameterList typeParameterList = function.getTypeParameterList();
if ((typeParameterList == null && isAncestor(function.getAnnotation(), element, false)
|| isAncestor(function.getTypeComment(), element, false))) {
return nextOwner;
}
}

View File

@@ -241,6 +241,11 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
final ScopeOwner resolvedOwner = processor.getOwner();
final Collection<PsiElement> resolvedElements = processor.getElements();
if (referenceOwner instanceof PyTypeParameterListOwner typeParameterListOwner) {
tryMatchTypeParametersInDeclaration(referencedName, typeParameterListOwner, realContext, resultList, typeEvalContext);
}
if (resolvedOwner != null && !resolvedElements.isEmpty() && !ControlFlowCache.getScope(resolvedOwner).isGlobal(referencedName)) {
if (resolvedOwner == referenceOwner) {
final List<Instruction> instructions = getLatestDefinitions(referencedName, resolvedOwner, realContext);
@@ -269,7 +274,9 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
resolveInParentScope = () -> ScopeUtil.getScopeOwner(resolvedOwner);
}
else {
unreachableLocalDeclaration = true;
if (resultList.isEmpty()) {
unreachableLocalDeclaration = true;
}
}
}
else if (referenceOwner != null) {
@@ -310,7 +317,18 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
continue;
}
if (definer == null) {
resultList.poke(resolved, getRate(resolved, typeEvalContext));
if (resolved instanceof PyTypeParameter) {
if (resolvedOwner != referenceOwner ||
PsiTreeUtil.getParentOfType(realContext, PyAnnotation.class, PyTypeCommentOwner.class) != null) {
resultList.poke(resolved, RatedResolveResult.RATE_HIGH);
}
else {
resultList.poke(resolved, RatedResolveResult.RATE_LOW);
}
}
else {
resultList.poke(resolved, getRate(resolved, typeEvalContext));
}
}
else {
resultList.poke(definer, getRate(definer, typeEvalContext));
@@ -373,6 +391,22 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
return results;
}
private static void tryMatchTypeParametersInDeclaration(@NotNull String referencedName,
PyTypeParameterListOwner owner,
PsiElement realContext,
ResolveResultList resultList,
TypeEvalContext typeEvalContext) {
PyTypeParameterList typeParameterList = owner.getTypeParameterList();
if (typeParameterList != null && PsiTreeUtil.getParentOfType(realContext, PyTypeParameterListOwner.class) != null) {
typeParameterList.getTypeParameters()
.stream().filter(typeParameter -> typeParameter.getName() != null && typeParameter.getName().equals(referencedName))
.forEach(typeParameter -> {
resultList.add(new RatedResolveResult(getRate(typeParameter, typeEvalContext), typeParameter));
}
);
}
}
@NotNull
private ResolveResultList resolveByReferenceResolveProviders() {
final ResolveResultList results = new ResolveResultList();

View File

@@ -87,7 +87,7 @@ public final class PyResolveUtil {
public static void scopeCrawlUp(@NotNull PsiScopeProcessor processor, @Nullable ScopeOwner scopeOwner,
@Nullable ScopeOwner originalScopeOwner, @Nullable String name, @Nullable PsiElement roof) {
while (scopeOwner != null) {
if (!(scopeOwner instanceof PyClass) || scopeOwner == originalScopeOwner) {
if (!(scopeOwner instanceof PyClass) || scopeOwner == originalScopeOwner || ((PyClass)scopeOwner).getTypeParameterList() != null) {
final Scope scope = ControlFlowCache.getScope(scopeOwner);
if (name != null) {
final boolean includeNestedGlobals = scopeOwner instanceof PyFile;

View File

@@ -0,0 +1,4 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
class Clazz[T]:
T = 1
<ref>

View File

@@ -0,0 +1,4 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
@dec(list[ T ]) # Runtime error: 'T' is not defined
<ref>
def func3[T](): ...

View File

@@ -0,0 +1,5 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
T = 0
def func2[T](a = list[ T ]): ... # Runtime error: 'T' is not defined
<ref>

View File

@@ -0,0 +1,6 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
T = 0
class Clazz[T]:
T = 1
<ref>

View File

@@ -0,0 +1,14 @@
print(T) # Prints 0
class Outer[T]:
T = 1
# T refers to the local variable scoped to class 'Outer'
print(T) # Prints 1
class Inner1:
T = 2
# T refers to the local type variable within 'Inner1'
print(T) # Prints 2
<ref>

View File

@@ -0,0 +1,5 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
class ClassA[T](BaseClass[T], param = Foo[T]): ... # OK
print(T) # Runtime error: 'T' is not defined
<ref>

View File

@@ -0,0 +1,6 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
def func1[T](a: T) -> T:
pass
print(T)
<ref>

View File

@@ -0,0 +1,7 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
T = 0
type a[T] = List[T]
print(T)
<ref>

View File

@@ -0,0 +1,24 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
T = 0
# T refers to the global variable
print(T) # Prints 0
class Outer:
T = 1
# T refers to the local variable scoped to class 'Outer'
print(T) # Prints 1
class Inner1:
T = 2
# T refers to the local type variable within 'Inner1'
print(T) # Prints 2
def inner_method(self):
# T refers to the type parameter scoped to class 'Outer';
# If 'Outer' did not use the new type parameter syntax,
# this would instead refer to the global variable 'T'
print(T) # Prints 'T'
<ref>

View File

@@ -0,0 +1,24 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
T = 0
# T refers to the global variable
print(T) # Prints 0
class Outer[T]:
T = 1
# T refers to the local variable scoped to class 'Outer'
print(T) # Prints 1
class Inner1:
T = 2
# T refers to the local type variable within 'Inner1'
print(T) # Prints 2
def inner_method(self):
# T refers to the type parameter scoped to class 'Outer';
# If 'Outer' did not use the new type parameter syntax,
# this would instead refer to the global variable 'T'
print(T) # Prints 'T'
<ref>

View File

@@ -0,0 +1,34 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
T = 0
# T refers to the global variable
print(T) # Prints 0
class Outer[T]:
T = 1
# T refers to the local variable scoped to class 'Outer'
print(T) # Prints 1
class Inner1:
T = 2
# T refers to the local type variable within 'Inner1'
print(T) # Prints 2
def inner_method(self):
# T refers to the type parameter scoped to class 'Outer';
# If 'Outer' did not use the new type parameter syntax,
# this would instead refer to the global variable 'T'
print(T) # Prints 'T'
def outer_method(self):
T = 3
# T refers to the local variable within 'outer_method'
print(T) # Prints 3
def inner_func():
# T refers to the variable captured from 'outer_method'
print(T) # Prints 3
<ref>

View File

@@ -0,0 +1,3 @@
type myType = str
myType
<ref>

View File

@@ -0,0 +1,3 @@
class Clazz[T]:
x: T = 0
<ref>

View File

@@ -0,0 +1,3 @@
class Clazz[T](Base[ T ]):
<ref>
pass

View File

@@ -0,0 +1,3 @@
def foo[T](a: T):
<ref>
pass

View File

@@ -0,0 +1,4 @@
class Clazz[T]:
def method(self, x: T):
<ref>
pass

View File

@@ -0,0 +1,5 @@
class Outer[T]:
class Inner1:
print(T)
<ref>

View File

@@ -0,0 +1,4 @@
def foo[T]():
def inner():
x: T = 0
<ref>

View File

@@ -0,0 +1,4 @@
class Clazz[T]:
def method(self) -> T:
<ref>
pass

View File

@@ -0,0 +1,3 @@
def foo[T](a: T) -> T:
<ref>
pass

View File

@@ -0,0 +1,2 @@
type MyType[T] = List[ T ]
<ref>

View File

@@ -0,0 +1,4 @@
def foo[T](a: T):
x: T = 0
<ref>
pass

View File

@@ -0,0 +1,6 @@
# see https://peps.python.org/pep-0695/#type-parameter-scopes
T = 0
def foo[T]():
print(T)
<ref>