PY-82699 Type parameter is not shadowed by a local variable

GitOrigin-RevId: 973100ef1aae3e5ebd55c514e13565eae8f7985e
This commit is contained in:
Petr
2025-07-22 19:28:12 +00:00
committed by intellij-monorepo-bot
parent 9cffac56b3
commit 572dffa91e
18 changed files with 103 additions and 52 deletions
@@ -1050,6 +1050,9 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.addNodeAndCheckPending(instruction);
}
@Override
public void visitPyTypeParameterList(@NotNull PyTypeParameterList node) { }
private void visitCondition(@NotNull PyExpression expression, @NotNull Instruction trueNode, @NotNull Instruction falseNode) {
TrueFalseNodes prevTrueFalseNodes = myTrueFalseNodes;
myTrueFalseNodes = new TrueFalseNodes(trueNode, falseNode);
@@ -114,6 +114,10 @@ public final class PyUnboundLocalVariableInspection extends PyInspection {
}
final PsiPolyVariantReference ref = node.getReference(getResolveContext());
final PsiElement resolved = ref.resolve();
// type parameter list is not included in CFG
if (resolved instanceof PyTypeParameter) {
return;
}
final boolean isBuiltin = PyBuiltinCache.getInstance(node).isBuiltin(resolved);
if (owner instanceof PyClass) {
if (isBuiltin || ScopeUtil.getDeclarationScopeOwner(owner, name) != null) {
@@ -97,8 +97,15 @@ public final class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor {
}
private void collectAllWrites(ScopeOwner owner) {
final Instruction[] instructions = ControlFlowCache.getControlFlow(owner).getInstructions();
Set<PsiElement> scopeWrites = new HashSet<>();
// type parameter list is not included in CFG
if (owner instanceof PyTypeParameterListOwner typeParameterListOwner) {
PyTypeParameterList typeParameterList = typeParameterListOwner.getTypeParameterList();
if (typeParameterList != null) {
scopeWrites.addAll(typeParameterList.getTypeParameters());
}
}
final Instruction[] instructions = ControlFlowCache.getControlFlow(owner).getInstructions();
for (Instruction instruction : instructions) {
final PsiElement element = instruction.getElement();
if (element instanceof PyFunction && owner instanceof PyFunction) {
@@ -236,7 +236,10 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
final ScopeOwner resolvedOwner = processor.getOwner();
final Collection<PsiElement> resolvedElements = processor.getElements();
if (resolvedOwner != null && !resolvedElements.isEmpty() && !ControlFlowCache.getScope(resolvedOwner).isGlobal(referencedName)) {
if (resolvedOwner != null &&
!processor.isTypeParameterScope() &&
!resolvedElements.isEmpty() &&
!ControlFlowCache.getScope(resolvedOwner).isGlobal(referencedName)) {
if (resolvedOwner == referenceOwner && referenceAnchor != null) {
final List<Instruction> instructions = getLatestDefinitions(referencedName, resolvedOwner, referenceAnchor);
// TODO: Use the results from the processor as a cache for resolving to latest defs
@@ -8,10 +8,7 @@ import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.psi.PyFromImportStatement;
import com.jetbrains.python.psi.PyImportElement;
import com.jetbrains.python.psi.PyImportedNameDefiner;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ResolveResultList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -27,6 +24,7 @@ public class PyResolveProcessor implements PsiScopeProcessor {
private final @NotNull Map<PsiElement, PyImportedNameDefiner> myResults = Maps.newLinkedHashMap();
private final @NotNull Map<PsiElement, PyImportedNameDefiner> myImplicitlyImportedResults = Maps.newLinkedHashMap();
protected @Nullable ScopeOwner myOwner;
private boolean myTypeParameterScope;
public PyResolveProcessor(@NotNull String name) {
this(name, false);
@@ -79,6 +77,10 @@ public class PyResolveProcessor implements PsiScopeProcessor {
return myOwner;
}
public boolean isTypeParameterScope() {
return myTypeParameterScope;
}
private @NotNull List<RatedResolveResult> resolveInImportedNameDefiner(@NotNull PyImportedNameDefiner definer) {
if (myLocalResolve) {
final PyImportElement importElement = PyUtil.as(definer, PyImportElement.class);
@@ -96,8 +98,9 @@ public class PyResolveProcessor implements PsiScopeProcessor {
final ScopeOwner owner = ScopeUtil.getScopeOwner(definer != null ? definer : element);
if (myOwner == null) {
myOwner = owner;
myTypeParameterScope = element instanceof PyTypeParameter;
}
final boolean sameScope = owner == myOwner;
final boolean sameScope = owner == myOwner && (element instanceof PyTypeParameter) == myTypeParameterScope;
if (sameScope) {
addResult(element, definer);
}
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.psi.resolve;
import com.google.common.collect.Iterables;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
@@ -117,7 +118,10 @@ public final class PyResolveUtil {
else {
namedElements = scope.getNamedElements();
}
for (PsiElement element : ContainerUtil.concat(namedElements, scope.getImportedNameDefiners())) {
Iterable<PsiElement> elements = Iterables.concat(Iterables.filter(namedElements, t -> !(t instanceof PyTypeParameter)),
scope.getImportedNameDefiners(),
Iterables.filter(namedElements, PyTypeParameter.class));
for (PsiElement element : elements) {
if (isClassLevelDefinitionInvisibleToReference(element, scopeOwner, originalScopeOwner)) continue;
if (!processor.execute(element, ResolveState.initial())) {
return;
@@ -1,12 +0,0 @@
0(1) element: null
1(2) element: PyTypeParameter
2(3) WRITE ACCESS: T
3(4) element: PySubscriptionExpression
4(5) READ ACCESS: list
5(6) READ ACCESS: T
6(7) WRITE ACCESS: x
7(8) READ ACCESS: T
8(9) element: PyReturnStatement
9(10) element: PySubscriptionExpression
10(11) READ ACCESS: x
11() element: null
@@ -0,0 +1,10 @@
0(1) element: null
1(2) element: PySubscriptionExpression
2(3) READ ACCESS: list
3(4) READ ACCESS: T
4(5) WRITE ACCESS: x
5(6) READ ACCESS: T
6(7) element: PyReturnStatement
7(8) element: PySubscriptionExpression
8(9) READ ACCESS: x
9() element: null
@@ -1,12 +1,6 @@
0(1) element: null
1(2) element: PyTypeParameter
2(3) WRITE ACCESS: T
3(4) READ ACCESS: str
4(5) element: PyTypeParameter
5(6) WRITE ACCESS: U
6(7) READ ACCESS: int
7(8) element: PySubscriptionExpression
8(9) READ ACCESS: Union
9(10) READ ACCESS: T
10(11) READ ACCESS: U
11() element: null
1(2) element: PySubscriptionExpression
2(3) READ ACCESS: Union
3(4) READ ACCESS: T
4(5) READ ACCESS: U
5() element: null
@@ -1,10 +1,6 @@
0(1) element: null
1(2) element: PyTypeParameter
2(3) WRITE ACCESS: T
3(4) element: PyTypeParameter
4(5) WRITE ACCESS: U
5(6) element: PySubscriptionExpression
6(7) READ ACCESS: BaseClass
7(8) READ ACCESS: T
8(9) element: PyPassStatement
9() element: null
1(2) element: PySubscriptionExpression
2(3) READ ACCESS: BaseClass
3(4) READ ACCESS: T
4(5) element: PyPassStatement
5() element: null
@@ -1,11 +1,7 @@
0(1) element: null
1(2) element: PyTypeParameter
2(3) WRITE ACCESS: T
3(4) element: PyTypeParameter
4(5) WRITE ACCESS: U
5(6) READ ACCESS: T
6(7) WRITE ACCESS: a
7(8) READ ACCESS: U
8(9) WRITE ACCESS: b
9(10) element: PyPassStatement
10() element: null
1(2) READ ACCESS: T
2(3) WRITE ACCESS: a
3(4) READ ACCESS: U
4(5) WRITE ACCESS: b
5(6) element: PyPassStatement
6() element: null
@@ -0,0 +1,6 @@
def outer[T]():
def inner():
print(T)
# <ref>
T = -1
@@ -0,0 +1,4 @@
def f[T]():
print(T)
# <ref>
T = 1
@@ -895,6 +895,16 @@ public class Py3ResolveTest extends PyResolveTestCase {
assertResolvesToItself();
}
// PY-82699
public void testTypeParameterRebindToLocalVariableInEnclosingScope() {
assertResolvesTo(PyTargetExpression.class, "T");
}
// PY-82699
public void testTypeParameterRebindToLocalVariableInSameScope() {
assertUnresolved();
}
private void assertResolvesToItself() {
PsiElement resolved = doResolve();
PsiReference reference = PyResolveTestCase.findReferenceByMarker(myFixture.getFile());
@@ -3933,6 +3933,17 @@ public class Py3TypeTest extends PyTestCase {
""");
}
// PY-82699
public void testTypeParameterRebind() {
doTest("int", """
def outer[T]() -> None:
def inner() -> None:
expr = T
T = -1
""");
}
private void doTest(final String expectedType, final String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final PyExpression expr = myFixture.findElementByText("expr", PyExpression.class);
@@ -578,8 +578,8 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase {
doTest();
}
// PY-61877
public void testNewStyleGenericFunctionAnnotationsAreIncludedInItsGraph() {
// PY-61877 PY-82699
public void testNewStyleGenericFunctionAnnotationsAreNotIncludedInItsGraph() {
doTestFirstStatement();
}
@@ -464,4 +464,16 @@ public class Py3UnresolvedReferencesInspectionTest extends PyInspectionTestCase
);
}
// PY-82699
public void testTypeParameterRebind() {
doTestByText("""
def outer1[T]() -> None:
print(<error descr="Unresolved reference 'T'">T</error>)
T = 1
def outer2[T]() -> None:
print(T)
""");
}
}