mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
PY-62608 PEP 695 Type Parameter Syntax: Resolve and scoping for type parameters in type aliases
GitOrigin-RevId: be532456e7a9c470d5d0a2770a7a2eb9b9b6e8de
This commit is contained in:
committed by
intellij-monorepo-bot
parent
65cc3ddfd0
commit
97185d17c8
@@ -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");
|
||||
// });
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# see https://peps.python.org/pep-0695/#type-parameter-scopes
|
||||
class Clazz[T]:
|
||||
T = 1
|
||||
<ref>
|
||||
@@ -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](): ...
|
||||
@@ -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>
|
||||
@@ -0,0 +1,6 @@
|
||||
# see https://peps.python.org/pep-0695/#type-parameter-scopes
|
||||
T = 0
|
||||
|
||||
class Clazz[T]:
|
||||
T = 1
|
||||
<ref>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,6 @@
|
||||
# see https://peps.python.org/pep-0695/#type-parameter-scopes
|
||||
def func1[T](a: T) -> T:
|
||||
pass
|
||||
|
||||
print(T)
|
||||
<ref>
|
||||
@@ -0,0 +1,7 @@
|
||||
# see https://peps.python.org/pep-0695/#type-parameter-scopes
|
||||
T = 0
|
||||
|
||||
type a[T] = List[T]
|
||||
|
||||
print(T)
|
||||
<ref>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
3
python/testData/resolve/TypeAliasStatement.py
Normal file
3
python/testData/resolve/TypeAliasStatement.py
Normal file
@@ -0,0 +1,3 @@
|
||||
type myType = str
|
||||
myType
|
||||
<ref>
|
||||
@@ -0,0 +1,3 @@
|
||||
class Clazz[T]:
|
||||
x: T = 0
|
||||
<ref>
|
||||
@@ -0,0 +1,3 @@
|
||||
class Clazz[T](Base[ T ]):
|
||||
<ref>
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
def foo[T](a: T):
|
||||
<ref>
|
||||
pass
|
||||
@@ -0,0 +1,4 @@
|
||||
class Clazz[T]:
|
||||
def method(self, x: T):
|
||||
<ref>
|
||||
pass
|
||||
@@ -0,0 +1,5 @@
|
||||
class Outer[T]:
|
||||
|
||||
class Inner1:
|
||||
print(T)
|
||||
<ref>
|
||||
@@ -0,0 +1,4 @@
|
||||
def foo[T]():
|
||||
def inner():
|
||||
x: T = 0
|
||||
<ref>
|
||||
@@ -0,0 +1,4 @@
|
||||
class Clazz[T]:
|
||||
def method(self) -> T:
|
||||
<ref>
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
def foo[T](a: T) -> T:
|
||||
<ref>
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
type MyType[T] = List[ T ]
|
||||
<ref>
|
||||
@@ -0,0 +1,4 @@
|
||||
def foo[T](a: T):
|
||||
x: T = 0
|
||||
<ref>
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
# see https://peps.python.org/pep-0695/#type-parameter-scopes
|
||||
T = 0
|
||||
|
||||
def foo[T]():
|
||||
print(T)
|
||||
<ref>
|
||||
Reference in New Issue
Block a user