PY-85030 When constructing a structural type, make sure attribute qualifiers refer to the original parameter on all paths

We already had protection against reassignment to a parameter name,
but it didn't take into account that we also skip conditional branches and loops
to collect only definitely accessed attributes of an object, so if reassignments
took place under conditions, even in every branch, they didn't prevent us
from collecting subsequently accessed attributes.

GitOrigin-RevId: d41b8f4c3dbce6643d915923172d32a8e4e6466c
This commit is contained in:
Mikhail Golubev
2026-01-19 20:46:01 +00:00
committed by intellij-monorepo-bot
parent 9c3b187edf
commit 223a91c3ac
4 changed files with 49 additions and 3 deletions
@@ -271,6 +271,7 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
final Ref<Boolean> parameterWasReassigned = Ref.create(false);
final Ref<Boolean> noneComparison = Ref.create(false);
final PyResolveContext resolveContext = PyResolveContext.defaultContext(context);
if (owner != null && name != null) {
owner.accept(new PyRecursiveElementVisitor() {
@@ -378,8 +379,10 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
@Contract("null -> false")
private boolean isReferenceToParameter(@Nullable PsiElement element) {
if (element == null) return false;
final PsiReference reference = element.getReference();
return reference != null && reference.isReferenceTo(PyNamedParameterImpl.this);
if (!(element instanceof PyReferenceExpression || element instanceof PyTargetExpression)) return false;
if (((PyQualifiedExpression)element).isQualified()) return false;
List<@Nullable PsiElement> definitions = PyUtil.multiResolveTopPriority(element, resolveContext);
return ContainerUtil.all(definitions, e -> e == PyNamedParameterImpl.this);
}
});
}
@@ -1,4 +1,4 @@
def foo(smth, param):
if smth:
param = ""
print(param.<weak_warning descr="Member 'str' of 'Union[{smth}, str]' does not have attribute 'smth'">smth</weak_warning>())
print(param.<weak_warning descr="Member 'str' of 'Union[str, Any]' does not have attribute 'smth'">smth</weak_warning>())
@@ -4676,6 +4676,17 @@ public class Py3TypeTest extends PyTestCase {
""");
}
// PY-85030
public void testStructuralTypesAttributeAccessAfterTypeNarrowingAndReassignmentInIf() {
doTest("(p: Any) -> None", """
def f(p):
if isinstance(p, int):
p = "foo"
x = p.lower()
expr = f
""");
}
private void doTest(final String expectedType, final String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final PyExpression expr = myFixture.findElementByText("expr", PyExpression.class);
@@ -4125,5 +4125,37 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
_: C2 = c1
""");
}
// PY-85030
public void testStructuralTypeAndStrictUnion() {
doTestByText("""
responses = {
100: "abc",
}
def process(status):
if isinstance(status, int):
status = responses[status]
return status.lower().replace(" ", "-")
def do(arg):
title = "abc" if arg else 100
return process(title)
""");
}
// PY-85030
public void testStructuralTypeAndDefiniteReassignmentUnderCondition() {
doTestByText("""
def f(p):
if p:
p = "foo"
else:
p = "bar"
return p.lower()
f(42)
""");
}
}